nexus/.planning/phases/24-search-history-branching/24-VERIFICATION.md

20 KiB
Raw Blame History

phase verified status score gaps human_verification
24-search-history-branching 2026-04-01T00:00:00Z gaps_found 3/4 success criteria verified
truth status reason artifacts missing
User can export any conversation as a Markdown file or as a JSON file partial Server supports both formats (markdown + json) and chatApi.exportConversation accepts format param, but ChatPanel only renders a single 'Export as Markdown' button (calls handleExport('markdown')). No UI control triggers JSON export.
path issue
ui/src/components/ChatPanel.tsx Only one export button at line 261: onClick={() => handleExport('markdown')}. JSON export path (handleExport('json')) is never called from any UI element.
Add a second export button (or dropdown) in ChatPanel header to trigger handleExport('json') — the handler, API client method, server route, and service all exist and work; only the UI trigger is missing
test expected why_human
Confirm Cmd+K -> 'Search chat messages' -> type query -> click result scrolls to the correct message Search overlay opens, results appear for matching terms within ~500ms, clicking a result switches to that conversation and the virtualizer scrolls to the target message Cannot test keyboard shortcuts, overlay rendering, or virtualizer scroll behavior programmatically without running the app
test expected why_human
Confirm bookmark toggle persists: hover a message, click the bookmark icon (fills solid), refresh page, verify icon is still filled Bookmark state survives page reload, indicating the POST /conversations/:id/bookmarks endpoint is being called and the DB write is real Requires live browser interaction and server running
test expected why_human
Confirm branch-on-edit: send two messages, click edit on the first, submit — verify a new branch conversation appears in the sidebar with a GitBranch icon Branch conversation is created, sidebar shows it indented under the original with a GitBranch indicator, branch selector bar appears above the message list Requires live server, active agent streaming session, and visual inspection of the sidebar grouping
test expected why_human
Confirm Markdown export downloads a .md file with agent names (not UUIDs) in the headers Browser downloads a file named <slug>-<date>.md; assistant messages show agent name (e.g. 'Brainstormer') not a UUID Requires browser file download and manual inspection of content

Phase 24: Search, History & Branching — Verification Report

Phase Goal: Users can find any message across all conversations in under 500ms, export conversations, bookmark key messages, and branch from any point in a conversation Verified: 2026-04-01 Status: gaps_found — 1 gap blocking complete goal achievement Re-verification: No — initial verification


Goal Achievement

Success Criteria (from ROADMAP.md)

# Criterion Status Evidence
1 Cmd+K opens search overlay; results returned in <500ms across 10,000+ messages ? HUMAN NEEDED FTS pipeline fully wired (tsvector GIN index → plainto_tsquery → ts_rank → ChatSearchDialog → CommandPalette custom event); runtime perf requires human test
2 User can bookmark any message and navigate to bookmarked messages ✓ VERIFIED ChatMessageBookmark renders on every message via ChatMessageActions; useToggleBookmark mutation calls POST /bookmarks; ChatBookmarkList with onNavigate wired into ChatPanel
3 Editing a message with a response creates a branch; user can switch branches ✓ VERIFIED handleEdit checks editedIdx < messages.length - 1 then calls chatApi.branchConversation; ChatBranchSelector rendered when branches.length > 0; ChatConversationList shows GitBranch icon + pl-4 indent
4 User can export as Markdown or JSON ✗ FAILED Server route and service support both formats; chatApi.exportConversation accepts format param; ChatPanel only exposes markdown button — JSON export has no UI trigger

Score: 3/4 success criteria verified (criterion 1 needs human runtime check; criterion 4 has a confirmed code gap)


Required Artifacts

Plan 00 — DB Migrations, Schema, Shared Types

Artifact Expected Status Details
packages/db/src/migrations/0050_add_branch_columns.sql Branch columns migration ✓ VERIFIED Contains parent_conversation_id FK + branch_from_message_id + index
packages/db/src/migrations/0051_add_message_search_vector.sql tsvector + GIN index migration ✓ VERIFIED content_search generated column + GIN index present
packages/db/src/migrations/0052_create_chat_message_bookmarks.sql Bookmarks table migration ✓ VERIFIED Table + two compound indexes for company+message and company+conversation
packages/db/src/schema/chat_message_bookmarks.ts Drizzle schema for bookmarks ✓ VERIFIED chatMessageBookmarks exported; compound indexes defined
packages/db/src/schema/chat_conversations.ts Branch columns added ✓ VERIFIED parentConversationId with AnyPgColumn for self-referential FK; branchFromMessageId; parentIdx
packages/db/src/schema/index.ts Re-exports chatMessageBookmarks ✓ WIRED Line 61: export { chatMessageBookmarks } from "./chat_message_bookmarks.js"
packages/shared/src/types/chat.ts Search, bookmark, branch types ✓ VERIFIED ChatMessageSearchResult, ChatBookmark, ChatBookmarkWithMessage, ChatBookmarkToggleResponse; parentConversationId on ChatConversation
packages/shared/src/validators/chat.ts searchMessagesSchema, branchConversationSchema ✓ VERIFIED Both validators present with correct shapes
packages/shared/src/index.ts Re-exports new types and validators ✓ WIRED Lines 565566, 581: all new symbols exported
server/src/__tests__/chat-service.test.ts Wave 0 test stubs ✓ VERIFIED 4 describe blocks with it.todo entries (searchMessages, toggleBookmark, branchConversation, exportConversation)

Plan 01 — Server Service Methods and Routes

Artifact Expected Status Details
server/src/services/chat.ts Six service methods ✓ VERIFIED searchMessages (tsvector FTS), toggleBookmark (transactional), getBookmarks (join), branchConversation (message copy), listBranches, exportConversation (agent LEFT JOIN)
server/src/routes/chat.ts Six route handlers ✓ VERIFIED GET /messages/search, POST /bookmarks, GET /bookmarks, POST /branch, GET /branches, GET /export — all with assertBoard guard

Plan 02 — UI API Client, Hooks, Components

Artifact Expected Status Details
ui/src/api/chat.ts Six new chatApi methods ✓ VERIFIED searchMessages, toggleBookmark, getBookmarks, branchConversation, listBranches, exportConversation (returns URL string)
ui/src/hooks/useChatSearch.ts Debounced FTS query hook ✓ VERIFIED placeholderData, staleTime: 30_000, enabled when query >= 2 chars
ui/src/hooks/useChatBookmarks.ts Bookmark query + mutation hooks ✓ VERIFIED useChatBookmarks + useToggleBookmark; invalidates both ["chat","bookmarks"] and ["chat","search"]
ui/src/components/ChatSearchDialog.tsx CommandDialog FTS overlay ✓ VERIFIED Uses CommandDialog, shouldFilter={false}, useChatSearch, HighlightedText (XSS-safe)
ui/src/components/ChatMessageBookmark.tsx Bookmark toggle button ✓ VERIFIED fill-current on isBookmarked; aria-label toggles; ghost h-6 w-6 sizing
ui/src/components/ChatBookmarkList.tsx Scrollable bookmark list ✓ VERIFIED useChatBookmarks; skeleton loading; empty state; onNavigate callback
ui/src/components/ChatBranchSelector.tsx Horizontal branch picker ✓ VERIFIED GitBranch icon; bg-accent for active; renders null when no branches

Plan 03 — Integration Wiring

Artifact Expected Status Details
ui/src/context/ChatPanelContext.tsx scrollToMessageId state ✓ VERIFIED State + setter in interface, provider value, and useState
ui/src/components/ChatPanel.tsx Full feature integration ✓ VERIFIED ChatSearchDialog, ChatBranchSelector, export (markdown only — see gap), bookmarks panel, branch-on-edit, useToggleBookmark, bookmarkedMessageIds Set
ui/src/components/ChatMessageList.tsx Scroll-to-message support ✓ VERIFIED useEffect on scrollToMessageId; virtualizer.scrollToIndex(index, {align:"center"}); resets to null after scroll
ui/src/components/ChatMessageActions.tsx Bookmark button on messages ✓ VERIFIED ChatMessageBookmark rendered as last action; onBookmark + isBookmarked props
ui/src/components/ChatMessage.tsx Bookmark prop threading ✓ VERIFIED onBookmark={id && onBookmark ? () => onBookmark(id) : undefined} — real messageId passed
ui/src/components/CommandPalette.tsx "Search chat messages" command item ✓ VERIFIED value="search-chat"; dispatches nexus:open-chat-search custom event
ui/src/components/ChatConversationList.tsx Branch indicators ✓ VERIFIED GitBranch icon + pl-4 indent when parentConversationId is non-null

From To Via Status Details
packages/db/src/schema/chat_message_bookmarks.ts packages/db/src/schema/index.ts re-export ✓ WIRED Line 61 of schema/index.ts
packages/shared/src/types/chat.ts packages/shared/src/index.ts re-export ✓ WIRED Lines 565581
server/src/routes/chat.ts server/src/services/chat.ts svc.searchMessages, svc.toggleBookmark, svc.branchConversation, svc.exportConversation ✓ WIRED All four service calls confirmed in route handlers
server/src/services/chat.ts packages/db/src/schema/chat_message_bookmarks.ts import chatMessageBookmarks ✓ WIRED Line 3 of services/chat.ts
ui/src/hooks/useChatSearch.ts ui/src/api/chat.ts chatApi.searchMessages ✓ WIRED Line 7 of useChatSearch.ts
ui/src/components/ChatSearchDialog.tsx ui/src/hooks/useChatSearch.ts useChatSearch ✓ WIRED Line 3 import + line 78 usage
ui/src/components/ChatMessageBookmark.tsx ui/src/hooks/useChatBookmarks.ts useToggleBookmark ✓ WIRED Indirectly — onToggle is wired at ChatPanel → handleBookmark → toggleBookmark; ChatMessageBookmark itself only needs onToggle callback
ui/src/components/CommandPalette.tsx ui/src/components/ChatSearchDialog.tsx nexus:open-chat-search custom event ✓ WIRED CommandPalette dispatches; ChatPanel listens and sets searchOpen(true)
ui/src/context/ChatPanelContext.tsx ui/src/components/ChatMessageList.tsx scrollToMessageId ✓ WIRED useChatPanel() in ChatMessageList; useEffect scrolls virtualizer
ui/src/components/ChatPanel.tsx ui/src/components/ChatBranchSelector.tsx branches data from useQuery ✓ WIRED listBranches query feeds ChatBranchSelector; onSelectBranch calls setActiveConversationId
ui/src/components/ChatMessage.tsx ui/src/components/ChatMessageBookmark.tsx (via ChatMessageActions) onBookmark prop chain ✓ WIRED ChatPanel → handleBookmark → ChatMessageList → ChatMessage → ChatMessageActions → ChatMessageBookmark.onToggle
ui/src/components/ChatPanel.tsx JSON export handleExport("json") ✗ NOT_WIRED handleExport callback accepts "json" but no UI element calls it; only markdown button rendered

Data-Flow Trace (Level 4)

Artifact Data Variable Source Produces Real Data Status
server/src/services/chat.ts :: searchMessages rows Drizzle query with tsvector @@ plainto_tsquery, ts_rank ORDER BY, LIMIT Yes — real DB join + FTS WHERE clause ✓ FLOWING
server/src/services/chat.ts :: toggleBookmark existing SELECT then INSERT or DELETE in transaction Yes — real DB read-modify-write ✓ FLOWING
server/src/services/chat.ts :: branchConversation messagesToCopy SELECT lte(createdAt) + INSERT returning Yes — real DB copy + returns new conversation ✓ FLOWING
server/src/services/chat.ts :: exportConversation rows SELECT + LEFT JOIN agents Yes — all messages + agent names from DB ✓ FLOWING
ui/src/hooks/useChatSearch.ts data chatApi.searchMessages → GET /companies/:id/messages/search Yes — enabled when query >= 2 chars, real endpoint ✓ FLOWING
ui/src/hooks/useChatBookmarks.ts data chatApi.getBookmarks → GET /companies/:id/bookmarks Yes — real endpoint ✓ FLOWING
ui/src/components/ChatPanel.tsx :: bookmarkedMessageIds Set useChatBookmarks(companyId, activeConversationId).data Yes — rebuilt per-conversation from server data ✓ FLOWING
ui/src/components/ChatPanel.tsx :: branches ChatConversation[] useQuery → chatApi.listBranches → GET /conversations/:id/branches Yes — real endpoint ✓ FLOWING
ui/src/components/ChatPanel.tsx :: JSON export handleExport("json") N/A — UI trigger missing ✗ DISCONNECTED (no UI entry point)

Behavioral Spot-Checks

Behavior Method Result Status
All 8 Phase 24 commits exist in git git log for 8 commit hashes All 8 found (430bbbb8 through 2b526e78) ✓ PASS
Migration journal has 0050/0051/0052 entries grep in _journal.json All three tags present ✓ PASS
Shared package exports key symbols node -e require check ChatMessageSearchResult, ChatBookmark, searchMessagesSchema, branchConversationSchema — all FOUND ✓ PASS
searchMessages has real FTS WHERE clause grep in service "content_search" @@ plainto_tsquery with ts_rank ORDER BY found ✓ PASS
export route sets Content-Disposition header grep in routes Line 273: res.setHeader("Content-Disposition", ...) confirmed ✓ PASS
JSON export has UI trigger in ChatPanel grep for handleExport("json") Only markdown button at line 261; no JSON trigger ✗ FAIL

Requirements Coverage

Requirement Source Plan(s) Description Status Evidence
CHAT-07 00, 01, 02, 03 Full-text search across all conversations ✓ SATISFIED tsvector GIN index, plainto_tsquery service, ChatSearchDialog, Cmd+K integration all wired
CHAT-13 00, 01, 02, 03 Message bookmarks: mark important messages ✓ SATISFIED ChatMessageBookmark on every message, toggleBookmark service+route, ChatBookmarkList navigation
CHAT-14 01, 03 Conversation branching on edit ✓ SATISFIED branchConversation service, POST /branch route, handleEdit branch-on-edit logic, ChatBranchSelector
HIST-04 00, 01, 02, 03 Conversation export as Markdown or JSON ✗ BLOCKED Server and API client support both formats; ChatPanel only exposes Markdown button — JSON has no UI trigger
PERF-04 01 FTS returns results <500ms across 10,000+ messages ? NEEDS HUMAN GIN index in place; ts_rank ordering correct; runtime perf requires load test or browser timing

Phantom Requirement IDs in Plan Frontmatter

The following requirement IDs appear in plan frontmatter (24-00-PLAN.md through 24-03-PLAN.md) but do not exist in REQUIREMENTS.md:

  • HIST-07, HIST-08, HIST-09, HIST-10, HIST-11, HIST-12

REQUIREMENTS.md defines only HIST-01 through HIST-06. These IDs were invented in the plan documents but have no corresponding requirement definitions. The authoritative requirement set for Phase 24 per ROADMAP.md is: CHAT-07, CHAT-13, CHAT-14, HIST-04, PERF-04 — all of which are accounted for above.


Anti-Patterns Found

File Line Pattern Severity Impact
ui/src/components/ChatMessageActions.tsx 37, 71 messageId="" and conversationId="" passed to ChatMessageBookmark Info Not a functional stub — ChatMessageBookmark does not use these props in its body (destructures only isBookmarked and onToggle); real messageId flows via onToggle callback. Props are dead interface noise, not a bug.
ui/src/components/ChatMessageList.tsx (comment) // TODO: if message not found, best-effort only Info Intentional — plan specified this as acceptable for scroll-to on unpaginated messages. Does not block any success criterion.

No blocking stubs found. All core implementations contain real DB queries, real HTTP calls, and real state management.


Human Verification Required

1. Full-text search round-trip with scroll-to-message

Test: Press Cmd+K in the Nexus UI. Select "Search chat messages". Type a search term that exists in a past conversation message. Verify results appear with conversation title and message snippet. Click a result. Expected: ChatSearchDialog closes; the chat panel switches to the target conversation; the virtualizer scrolls to the target message (centered in view) within ~500ms total. Why human: Keyboard shortcut dispatch, dialog rendering, virtualizer scroll, and sub-500ms timing cannot be verified without a running browser session.

2. Bookmark persistence across page reload

Test: Open a conversation. Hover a message. Click the bookmark icon (should fill solid). Reload the page. Return to the same conversation and hover the same message. Expected: The bookmark icon is still filled, confirming the DB write via POST /conversations/:id/bookmarks persisted and the GET /companies/:id/bookmarks query on reload returns it. Why human: Requires live server, DB write, and page reload cycle.

3. Branch-on-edit creates visible branch in sidebar

Test: In a conversation with at least two messages (one user, one assistant reply), click edit on the user message, change the text, and submit. Expected: A new branch conversation appears in the sidebar with a GitBranch icon and pl-4 indent under the original. The branch selector bar appears above the message list. Clicking "Original" in the branch selector switches back to the original conversation. Why human: Requires an active agent session, streaming, and visual sidebar inspection.

4. Markdown export produces correct agent names (not UUIDs)

Test: In a conversation that involved a named agent, click the Download icon in the chat header. Open the downloaded .md file. Expected: The file is named <title-slug>-<date>.md; assistant messages show the agent's name (e.g. "Brainstormer") not a UUID; user messages show "You"; format matches **Speaker** (timestamp)\ncontent\n\n---. Why human: Requires browser file download and manual inspection of content.


Gaps Summary

1 gap blocks full goal achievement:

JSON export has no UI trigger (Success Criterion 4, HIST-04)

The complete JSON export pipeline exists — the server service (exportConversation), the Express route (GET /conversations/:id/export?format=json), and the API client method (chatApi.exportConversation(id, "json")) all work correctly. The handleExport callback in ChatPanel also accepts "json" as a format. However, ChatPanel.tsx only renders one export button (line 261) that hardcodes handleExport("markdown"). No UI element triggers handleExport("json").

Fix required: Add a second export button (or a dropdown with two options) in the ChatPanel header that calls handleExport("json"). The server, service, route, API client, and handler are all already correct — only the UI trigger is missing.


Verified: 2026-04-01 Verifier: Claude (gsd-verifier)