- Add onBookmark/isBookmarked props to ChatMessageActions
- Render ChatMessageBookmark as last action for user and assistant messages
- Add onBookmark/isBookmarked props to ChatMessage, thread to ChatMessageActions
- System messages do not receive bookmark actions
- Add scrollToMessageId/setScrollToMessageId to ChatPanelContext
- Add "Search chat messages" item to CommandPalette (dispatches nexus:open-chat-search)
- Integrate ChatSearchDialog, ChatBranchSelector, ChatBookmarkList into ChatPanel
- Add export buttons (Markdown) and bookmarks panel toggle in header
- Wire branch-on-edit: branchConversation called when editing message with subsequent replies
- Add scroll-to-message support in ChatMessageList via virtualizer.scrollToIndex
- Show GitBranch icon for branch conversations in ChatConversationList
- ChatSearchDialog: CommandDialog with shouldFilter=false for server-side FTS, term highlighting via React components
- ChatMessageBookmark: ghost icon button with fill-current for bookmarked state, aria-label toggle
- ChatBookmarkList: scrollable list with skeleton loading, empty state, navigation callbacks
- ChatBranchSelector: horizontal branch picker bar with GitBranch icon, active branch highlight
- GET /companies/:companyId/messages/search: FTS search with ZodError 400 guard
- POST /conversations/:id/bookmarks: toggle bookmark with UUID validation
- GET /companies/:companyId/bookmarks: list bookmarks with optional conversationId filter
- POST /conversations/:id/branch: branch conversation from message point
- GET /conversations/:id/branches: list child conversations
- GET /conversations/:id/export: download Markdown or JSON with Content-Disposition header
- searchMessages: tsvector FTS with ts_rank ordering, joins conversations for companyId scoping
- toggleBookmark: transactional insert-or-delete bookmark
- getBookmarks: joins bookmarks+messages+conversations, supports conversationId filter
- branchConversation: copies messages up to branch point into new child conversation
- listBranches: queries child conversations by parentConversationId
- exportConversation: LEFT JOINs agents for name resolution, produces Markdown or JSON with file headers
- POST /conversations/:id/handoff: resolves companyId, inserts handoff
message, creates issue from spec, inserts task_created message
- POST /conversations/:id/status-update: inserts status_update message
- Import issueService and handoffSchema; instantiate issueSvc in chatRoutes
- Both routes use assertBoard for authentication
- Add 23-01-SUMMARY.md with accomplishments and commit hashes
- Update STATE.md: advance to plan 4, update progress to 94%, log session
- Update ROADMAP.md: phase 23 at 3/4 summaries, In Progress
- 23-00-SUMMARY.md: DB migration + shared types + Wave 0 test stubs
- STATE.md: advance to plan 2/4, record metric and decision
- ROADMAP.md: update phase 23 progress (1/4 summaries)
- REQUIREMENTS.md: mark AGENT-01 through AGENT-07 and CHAT-09 complete
- ChatTaskCreatedBadge renders loading state and resolved badge with View task link
- ChatStatusUpdateBadge renders CheckCircle2 + agent completion text with task link
- useBrainstormerDefault returns general role agent ID with React Query deduplication
- Fix ChatMessageList synthetic streaming entry to include messageType: null
- Add message_type text column to chat_messages Drizzle schema
- Create SQL migration 0049_add_message_type.sql
- Update _journal.json with entries for idx 47, 48, 49
- Add messageType field to ChatMessage interface
- Add messageType to createMessageSchema (optional)
- Add handoffSchema and Handoff type to validators/chat.ts
- Re-export handoffSchema and Handoff from shared/src/index.ts
- ChatSpecCard renders 4-field spec (What/Why/Constraints/Success) with edit mode
- ChatSpecCard action row: Send to PM, Edit, Save as Draft buttons
- ChatSpecCard edit mode with aria-labels, Escape key discard, tab order
- ChatHandoffIndicator separator-style with flanking hr and aria-label
- ChatPanel integrates useStreamingChat, ChatAgentSelector, ChatStopButton
- Agent routing via resolveAgentFromContent for slash commands and @mentions
- handleEdit: editMessage + truncateMessagesAfter + re-stream with edited content
- handleRetry: finds actual prior user message, truncates from user message onward, re-streams
- Build agentMap from agents for message identity bars
- ChatInput: slash command popover (triggered by / at start of input)
- ChatInput: @mention popover (triggered by @word pattern)
- Input disabled during streaming with 'Waiting for response...' placeholder
- Stop button shown conditionally when isStreaming
- Agent selector in header for per-conversation agent switching
- Remove ScrollArea wrapper (replaced by virtualizer's own scroll in ChatMessageList)
- Create ChatMentionPopover (200px, opens upward, agent icon + name + role label)
- Agents filtered by query, max 5 visible, No agents found empty state
- 3 skeleton rows loading state, onOpenAutoFocus prevented for textarea focus
- Include agent-role-colors dependency for worktree build
- Add id, isAnyStreaming, onEdit, onRetry props to ChatMessageProps
- User messages show edit Pencil on hover via ChatMessageActions
- Edit pencil opens inline textarea with Save/Discard buttons
- Save edit calls onEdit(id, newContent), disabled when textarea empty
- Discard edit reverts to read-only bubble
- Assistant messages show retry RefreshCw via ChatMessageActions
- All edit/retry actions disabled when isAnyStreaming is true
- Update test stubs to reflect new prop surface
- ChatStopButton: centered outline button with Square icon and 'Stop generating' label
- ChatMessageActions: edit Pencil for user messages (absolute, group-hover)
- ChatMessageActions: retry RefreshCw for assistant messages (right-aligned, group-hover)
- Both action buttons return null when isStreaming is true
- Proper aria-labels and tooltips on all interactive elements
- Create ChatAgentSelector with Popover+Command dropdown
- Show active agent icon, name, and ChevronDown indicator
- 'Select agent' placeholder when no agent selected
- 'No agents configured' empty state via CommandEmpty
- Agent list shows icon, name, and role label per item
- Selection calls onAgentChange and PATCHes conversation via chatApi
- Role-specific colors from agentRoleColors applied to agent icons
- Loading state shows Skeleton placeholder
- Create chat.ts API client with updateConversation supporting agentId
- Create shared types/chat.ts with ChatMessage, ChatConversation types
- Create ChatCodeBlock prerequisite from phase-21 base
- TypeScript compiles clean
- Create ChatMessageIdentityBar with agent icon, name, timestamp, and streaming dot
- Create ChatStreamingCursor with animate-cursor-blink and aria-hidden
- Extend ChatMessage with agentName, agentIcon, agentRole, timestamp, isStreaming props
- Wrap assistant messages in group div for hover actions (Plan 03)
- Create agent-role-colors.ts with 11 distinct role colors (light + dark variants)
- Create ChatMarkdownMessage prerequisite from phase-21 base
- All 4 ChatMessageIdentityBar tests pass
- Add postMessageAndStream and savePartialMessage to chatApi (fetch ReadableStream for POST SSE)
- Create useStreamingChat hook with startStream, stop, streamingContent, isStreaming
- startTransition wraps token updates to avoid blocking user input (PERF-02)
- AbortController used for stop functionality (CHAT-12)
- stop() saves partial content with [stopped] suffix to DB
- Add @testing-library/react devDependency to enable renderHook testing
- 5 passing unit tests: token accumulation, lifecycle, stop/abort, error, null guard
- Add editMessage, truncateMessagesAfter, streamEcho methods to chatService
- Add POST /conversations/:id/stream SSE endpoint with flushHeaders before loop (PERF-02)
- Add PATCH /conversations/:id/messages/:msgId for message editing
- Add DELETE /conversations/:id/messages/after/:msgId for message truncation
- Import gt from drizzle-orm for createdAt comparison in truncateMessagesAfter
- Guard all res.write() calls with res.writable check (prevents write-after-end)