- Add boocode-lift-analysis.md: comprehensive 30-repo lift matrix across 25 domains - Add openspec/ change docs: domain2-code-intelligence, domain3-multi-agent, impeccable-wave, streaming-codeblocks - Update .gitignore: .impeccable/, .omo/, bun.lock, DESIGN.md, PRODUCT.md - Update dependencies in package.json + pnpm-lock.yaml - Update .codesight/ analysis cache
4.6 KiB
Context
BooCode's chat UI streams LLM responses over a per-session WebSocket. The current useSessionStream hook uses a flat applyFrame reducer that maps WS frames to a Message[] array. This works for the basic case but has limitations: reconnection during streaming replaces the entire message list (snapshot-refetch), frame ordering depends on implicit WS ordering, and there's no concept of channel isolation between text, tool calls, and status.
CodeBlock.tsx wraps Shiki's async codeToHtml with basic copy and a language label. No line numbers, no diff markers, no theme switching.
MessageList.tsx does three sequential passes (flatten → group → stampCapHits) on every message array. It uses framer-motion fadeSlideIn for all items with no virtualization — beyond ~200 messages the DOM cost is noticeable on mobile.
The component suite (MarkdownRenderer, MessageBubble, ToolCallLine, ToolCallGroup) evolved organically with inconsistent error handling and accessibility.
LangGraph (referenced conceptually, not as a dependency) uses typed channels with Pregel-style superstep execution, where each channel is a typed slot that receives deltas in deterministic order with checkpoints. We port the channel pattern — not the library.
Goals / Non-Goals
Goals:
- Channel-based streaming reducer with typed deltas and ordered frame processing
- CodeBlock with line numbers, diff gutter, theme toggle, word-wrap, collapsible, inline copy progress
- MessageList with virtualized rendering for 500+ messages and smoother animations
- Error boundaries on all render-heavy components so one failure doesn't crash the list
- Keyboard-navigable tool calls in ToolCallLine/ToolCallGroup
- Consistent
aria-labelprotocol across all action buttons
Non-Goals:
- Replacing Shiki with another highlighter (stays)
- Porting langgraphjs as a runtime dependency (pattern only)
- Server-side streaming changes outside the WS frame protocol
- Adding undo/redo for message mutations
- Full i18n of the UI
Decisions
-
Channel-based reducer over flat pattern matching. Each WS frame type maps to a channel (
text,tool_call,tool_result,status,error). Channels process independently but merge into a singleStreamStatevia deterministic ordering rules. This allows mid-stream reconnection without full snapshot — missed deltas can be replayed per-channel. -
react-virtuoso over react-window.
react-virtuosohandles variable-height items natively (markdown renders at unpredictable heights), supports sticky headers, and has a built-infollowOutputmode for streaming chat.react-windowrequires fixed heights or complex measurement. -
Diff mode via CSS gutter classes, not Shiki transformers. Shiki's
codeToHtmlwithtransformersadds diff line metadata, but the current async-only pipeline makes it hard to cache both diff and plain renders. Instead,CodeBlockwill detect adiff-language prefix, parse+/-line markers, and render gutter decorations via CSS classes — simpler and works with any Shiki theme. -
Error boundaries at two granularities.
MarkdownRendererandCodeBlockeach get a<MessageBoundary>wrapper that catches render errors and shows a "Rendering failed" fallback + retry button. A top-level<MessageListErrorBoundary>catches everything else and shows a compact "Something went wrong" bar. This prevents Shiki failures from taking down the entire transcript.
Risks / Trade-offs
- [Risk] Virtualized list breaks streaming cursor. When a new message arrives mid-stream, react-virtuoso's
followOutputmay skip ahead if the user is scrolled up reading history. Mitigation:followOutput="auto"only scrolls when the user is within a threshold of the bottom; manualscrollToIndexon new assistant turns whenisNearBottomRefis true. - [Risk] Channel ordering complexity. Multiple channels processing frames out of order could produce inconsistent state (e.g.,
tool_resultchannel fires before thetool_callchannel's frame arrives). Mitigation: Each channel frame carries a monotonicseqcounter from the server; the reducer buffers out-of-order frames per-channel and flushes only whenseqis contiguous. - [Risk] Shiki async loading delay. First paint for a code block waits for Shiki's WASM + theme load. Mitigation: Keep the existing
Suspense+ skeleton fallback pattern; preload Shiki in the app shell viaimport('shiki')at bootstrap. - [Trade-off] No code-block caching. Every re-render re-runs
codeToHtml. For large files this is wasteful. AMap<code+theme+lang, html>LRU cache (max 50 entries) avoids recomputation without adding a dependency.