- 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
46 lines
4.6 KiB
Markdown
46 lines
4.6 KiB
Markdown
## 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-label` protocol 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
|
|
|
|
1. **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 single `StreamState` via deterministic ordering rules. This allows mid-stream reconnection without full snapshot — missed deltas can be replayed per-channel.
|
|
|
|
2. **react-virtuoso over react-window.** `react-virtuoso` handles variable-height items natively (markdown renders at unpredictable heights), supports sticky headers, and has a built-in `followOutput` mode for streaming chat. `react-window` requires fixed heights or complex measurement.
|
|
|
|
3. **Diff mode via CSS gutter classes, not Shiki transformers.** Shiki's `codeToHtml` with `transformers` adds diff line metadata, but the current async-only pipeline makes it hard to cache both diff and plain renders. Instead, `CodeBlock` will detect a `diff-` language prefix, parse `+`/`-` line markers, and render gutter decorations via CSS classes — simpler and works with any Shiki theme.
|
|
|
|
4. **Error boundaries at two granularities.** `MarkdownRenderer` and `CodeBlock` each 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 `followOutput` may 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; manual `scrollToIndex` on new assistant turns when `isNearBottomRef` is true.
|
|
- **[Risk] Channel ordering complexity.** Multiple channels processing frames out of order could produce inconsistent state (e.g., `tool_result` channel fires before the `tool_call` channel's frame arrives). **Mitigation**: Each channel frame carries a monotonic `seq` counter from the server; the reducer buffers out-of-order frames per-channel and flushes only when `seq` is 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 via `import('shiki')` at bootstrap.
|
|
- **[Trade-off] No code-block caching.** Every re-render re-runs `codeToHtml`. For large files this is wasteful. A `Map<code+theme+lang, html>` LRU cache (max 50 entries) avoids recomputation without adding a dependency.
|