docs: boocode-lift-analysis, openspec change docs, codesight cache, deps

- 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
This commit is contained in:
2026-06-08 03:49:26 +00:00
parent 50de80ee75
commit 6fde7002aa
29 changed files with 3624 additions and 138 deletions

View File

@@ -0,0 +1,39 @@
## ADDED Requirements
### Requirement: Channel-based streaming reducer
The streaming layer SHALL dispatch WS frames into typed channels (`text`, `tool_call`, `tool_result`, `status`, `error`) instead of a flat `applyFrame` pattern match. Each channel SHALL produce deltas independently but merge into a single `StreamState` via a deterministic ordering function.
#### Scenario: Text delta dispatched to text channel
- **WHEN** a `delta` frame with `content` arrives
- **THEN** the text channel accumulates the chunk and the merged state appends it to the target message's content
#### Scenario: Tool call dispatched to tool_call channel
- **WHEN** a `tool_call` frame with a `tool_call` object arrives
- **THEN** the tool_call channel pushes it to `message.tool_calls` on the target message id
### Requirement: Mid-stream reconnection without snapshot
The hook SHALL support mid-stream reconnection by replaying missed deltas per-channel, using a monotonic `seq` counter on each frame. If the server cannot replay, a full `snapshot` frame SHALL be used as fallback.
#### Scenario: Reconnection with missed deltas
- **WHEN** the WebSocket reconnects during an active stream
- **THEN** the client sends the last processed `seq` per-channel and the server replays missed deltas
- **AND** if replay is unavailable, the server sends a `snapshot` frame and the reducer resets to that state
### Requirement: Out-of-order frame buffering
The reducer SHALL buffer out-of-order frames per-channel and flush them only when the sequence number is contiguous from the last known value.
#### Scenario: Frame arrives out of order
- **WHEN** a frame with `seq=5` arrives before `seq=4` for the same channel
- **THEN** the reducer holds `seq=5` in a per-channel buffer
- **AND** when `seq=4` arrives, both frames SHALL be applied in order
### Requirement: Channel frame schema in ws-frames
The `@boocode/contracts` ws-frames schema SHALL define typed channel delta frames with a `seq` field, a `channel` discriminator, and channel-specific payloads.
#### Scenario: Channel frame validates correctly
- **WHEN** a channel delta frame matches the schema
- **THEN** `WsFrameSchema.safeParse` returns success

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: Line numbers
`CodeBlock` SHALL render line numbers in a left gutter for blocks with >1 line. Line numbers SHALL be 1-indexed, right-aligned, and muted in color (`text-muted-foreground`). Long files (≥1000 lines) SHALL not show line numbers by default (toggle override).
#### Scenario: Multi-line block shows line numbers
- **WHEN** a code block with 5 lines renders
- **THEN** a left gutter shows numbers 1 through 5
- **AND** each number is right-aligned and muted
### Requirement: Diff gutter markers
When the language prefix starts with `diff-` (e.g. `diff-typescript`), `CodeBlock` SHALL parse leading `+` / `-` / (space) characters on each line. Lines starting with `+` SHALL get a green left border and background tint. Lines starting with `-` SHALL get a red left border and background tint. Common lines SHALL render normally. The `+`/`-` prefix characters SHALL be stripped from the rendered text.
#### Scenario: Diff block renders with gutter markers
- **WHEN** a code block with language `diff-typescript` contains lines `+ const x = 1` and `- const y = 2`
- **THEN** the first line has a green gutter marker and shows `const x = 1`
- **AND** the second line has a red gutter marker and shows `const y = 2`
### Requirement: Multi-theme toggle
`CodeBlock` SHALL display a theme toggle button that switches between `github-dark` and `github-light`. The initial theme SHALL match the app's current color scheme. The user's choice SHALL persist for the session (no localStorage).
#### Scenario: User toggles theme
- **WHEN** the user clicks the theme toggle button
- **THEN** the code block re-renders with the alternate Shiki theme
- **AND** the button icon reflects the current theme
### Requirement: Word-wrap toggle
`CodeBlock` SHALL provide a word-wrap toggle button. When active, long lines wrap; when inactive, they overflow with horizontal scrolling. The default state SHALL be no-wrap (scroll). The toggle SHALL persist per-code-block for the session.
#### Scenario: User toggles word wrap
- **WHEN** the user clicks the word-wrap toggle
- **THEN** long lines wrap to the container width
- **AND** clicking again restores horizontal scroll
### Requirement: Collapsible long blocks
`CodeBlock` SHALL auto-collapse blocks with ≥30 lines, showing the first 15 lines and a "Show {N} more lines" button. Clicking expands the full block. The collapsed state SHALL be indicated by a gradient fade at the cutoff.
#### Scenario: Long block collapses
- **WHEN** a code block has 50 lines
- **THEN** the first 15 lines render with a fade gradient at the bottom
- **AND** a "Show 35 more lines" button appears below
- **AND** clicking the button expands all 50 lines
### Requirement: Inline copy with progress
The copy button SHALL show an animated check icon on success and a brief "Copied" label that resolves after 1200ms. On copy failure, the button SHALL briefly show a red X before reverting.
#### Scenario: Copy succeeds
- **WHEN** the user clicks Copy and the clipboard write succeeds
- **THEN** the button shows a check icon and "Copied" label for 1200ms
#### Scenario: Copy fails
- **WHEN** the user clicks Copy and the clipboard write fails
- **THEN** the button shows a red X for 1200ms before reverting

View File

@@ -0,0 +1,55 @@
## ADDED Requirements
### Requirement: Error boundary on MarkdownRenderer
`MarkdownRenderer` SHALL be wrapped in a `<MessageBoundary>` that catches render errors from remark-gfm or react-markdown and shows a compact "Content rendering failed" fallback with a retry button.
#### Scenario: Markdown render throws
- **WHEN** `react-markdown` or `remark-gfm` throws during rendering
- **THEN** the error boundary catches the exception and renders a fallback UI
- **AND** the fallback shows "Content rendering failed" with a Retry button
- **AND** clicking Retry re-mounts `MarkdownRenderer` with the same content
### Requirement: Error boundary on CodeBlock
`CodeBlock` SHALL be wrapped in a `<MessageBoundary>` that catches errors from Shiki's `codeToHtml` and renders a plain-text `<pre>` fallback with the source code.
#### Scenario: Shiki highlight fails
- **WHEN** `codeToHtml` throws (e.g., unknown language, WASM load failure)
- **THEN** the error boundary catches the exception and renders a plain `<pre>` block with the original code
### Requirement: Loading skeleton for streaming messages
Messages with `status === 'streaming'` and no content yet SHALL render a pulse-animated skeleton placeholder instead of an empty bubble.
#### Scenario: Streaming message starts with no content
- **WHEN** a `message_started` frame arrives with `role: 'assistant'`
- **THEN** a skeleton placeholder renders (animated pulse bar) until the first `delta` frame arrives with content
### Requirement: Keyboard-navigable ToolCallLine
`ToolCallLine` SHALL support full keyboard navigation: `Tab` to focus, `Enter`/`Space` to toggle expand/collapse, `Escape` to collapse if expanded.
#### Scenario: User navigates tool call via keyboard
- **WHEN** the user presses `Tab` to focus a `ToolCallLine`
- **THEN** a visible focus ring appears
- **AND** pressing `Enter` toggles expand/collapse
- **AND** pressing `Escape` collapses it if expanded
### Requirement: Keyboard-navigable ToolCallGroup
`ToolCallGroup` SHALL support the same keyboard navigation as `ToolCallLine`. The group header SHALL be the focusable element.
#### Scenario: User navigates tool group via keyboard
- **WHEN** the user presses `Tab` to focus a `ToolCallGroup` header
- **THEN** a visible focus ring appears
- **AND** pressing `Enter` toggles expand/collapse of the group
### Requirement: Consistent aria-label protocol
Every interactive element in `ActionRow` (Copy, Resend, Regenerate, Fork, Delete, Restore, Pin) SHALL have an `aria-label` attribute that matches its `title` text.
#### Scenario: Action row button has aria-label
- **WHEN** an ActionRow button renders
- **THEN** it SHALL have an `aria-label` matching its `title`
- **AND** the label SHALL be unique within the message bubble

View File

@@ -0,0 +1,49 @@
## ADDED Requirements
### Requirement: Virtualized rendering
`MessageList` SHALL use `react-virtuoso` to virtualize rendering of messages beyond the viewport. Only visible messages and a configurable overscan buffer (default 5 items above/below) SHALL be in the DOM. The virtualizer SHALL support variable-height items (Markdown renders at unpredictable heights).
#### Scenario: Long transcript renders with virtualization
- **WHEN** a chat has 500 messages
- **THEN** only ~20 messages are in the DOM at any time (viewport ± overscan)
- **AND** scrolling maintains correct scroll position
#### Scenario: Streaming follows bottom by default
- **WHEN** a new streaming message arrives and the user is near the bottom
- **THEN** the list auto-scrolls to follow the streaming content
- **AND** if the user has scrolled up reading history, auto-scroll is suppressed
### Requirement: Smarter tool-call grouping
Consecutive tool runs of the same name SHALL collapse into a grouped control with a count header (same as current behavior for ≥3). Additionally, each group SHALL support independent expand/collapse state (not just the current all-or-nothing). User can expand a group to see individual calls, or collapse them back.
#### Scenario: Grouped tool calls are individually expandable
- **WHEN** a tool group with 5 `view_file` calls renders
- **THEN** the collapsed group shows "5 view_file calls" with an expand chevron
- **AND** clicking expand shows each call as a separate item
- **AND** clicking collapse returns to the grouped header
### Requirement: Stagger entrance animations
New messages SHALL enter with a staggered fade-slide animation. Each new message SHALL animate with `opacity: 0 → 1, y: 8 → 0`. Consecutive new messages (from a batch delta or snapshot) SHALL stagger by 40ms between items, up to a max of 500ms total stagger.
#### Scenario: Batch of messages arrives
- **WHEN** 10 new messages arrive in a `snapshot` frame
- **THEN** each message fades in sequentially with a 40ms stagger between them
- **AND** animation is skipped (zero-duration) when `prefers-reduced-motion` is set
### Requirement: Message pin/bookmark
Users SHALL be able to pin a message by clicking a pin icon in the message's ActionRow. Pinned messages SHALL be tracked locally via URL hash (`#pin=<messageId>`). Only one message can be pinned at a time. A pinned indicator SHALL show at the top of the message list with a "Jump to pinned" link.
#### Scenario: User pins a message
- **WHEN** the user clicks the pin icon on a message
- **THEN** the URL hash updates to `#pin=<messageId>`
- **AND** a pin indicator appears at the top of the list with "Jump to pinned" link
- **AND** clicking the pin icon again unpins and clears the hash
#### Scenario: Pinned message navigates on click
- **WHEN** the user clicks "Jump to pinned"
- **THEN** the list scrolls to the pinned message
- **AND** the pinned message briefly highlights