Files
kandev/apps/web/agent_chat_plan.md
T
2026-01-15 02:23:09 +00:00

932 lines
25 KiB
Markdown

# Task Chat Interface - Final Implementation Plan
Comprehensive plan to build a high-performance, feature-rich task chat interface with SSR, lazy loading, and command support.
**Status:** Updated after rebase from main (Jan 2026)
**Key Changes:**
- ✅ SSR already implemented - fetches last 10 comments on server
- ✅ TanStack Virtual v3.13.18 already integrated
- ✅ Basic tool call rendering already exists
- ✅ Pagination API implemented (limit/before/after/sort + has_more/cursor)
- ✅ Smart auto-scroll added (only scrolls when at bottom)
- ✅ Lazy loading added (scroll-to-top fetch for older messages)
- ✅ CodeMirror input implemented (markdown, line wrapping, shortcut adapter, Mod-Enter submit)
- ✅ Slash + mention autocomplete added (dummy command examples)
- ✅ Message rendering refactor with adapter-style components
- ✅ Inline diff rendering (metadata diff uses @git-diff-view/react when structured)
- ⚠️ Rich content rendering partially implemented (thinking/todos/diff metadata cards, status/error/progress)
- ❌ Rich content (thinking, todos, diffs) not fully implemented
---
## Architecture Overview
### Data Flow
```
1. Initial Load (SSR):
Server Component → HTTP GET /api/tasks/:id/comments?limit=10&sort=desc
→ Hydrate store with last 10 messages
→ Client renders, scrolled to bottom
2. WebSocket Subscription:
Client mounts → ws.subscribe(taskId)
→ Receives comment.added notifications
→ Appends new messages to store
→ Auto-scroll if at bottom
3. Lazy Loading (Scroll Up):
User scrolls to top → Detect via TanStack Virtual
→ If not all messages loaded: HTTP GET /api/tasks/:id/comments?before=:oldestId&limit=20
→ Prepend to store
→ Maintain scroll position
```
### Tech Stack
**Core:**
- React 18 + Next.js 14 (SSR)
- TanStack Virtual v3 (virtualization)
- Zustand + Immer (state)
- WebSocket (real-time)
**UI:**
- Tailwind CSS + shadcn/ui
- CodeMirror 6 (input + file editing)
- react-markdown + remark-gfm (display)
- @git-diff-view/react (existing)
**New Dependencies:**
```json
{
"@codemirror/view": "^6.34.1",
"@codemirror/state": "^6.4.1",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-markdown": "^6.3.1",
"@codemirror/autocomplete": "^6.18.1",
"@codemirror/commands": "^6.7.0",
"@codemirror/language": "^6.10.3",
"@codemirror/theme-one-dark": "^6.1.2",
"codemirror": "^6.0.1"
}
```
**Total:** ~150KB gzipped (unified solution for chat + file editing)
**Already Installed:**
- `@tanstack/react-virtual`: v3.13.18 ✅
- `react-markdown` + `remark-gfm` ✅
- `@git-diff-view/react` ✅
---
## Current Implementation Status
### ✅ Already Implemented (After Rebase)
1. **SSR (Server-Side Rendering)**
- `app/task/[id]/page.tsx` fetches comments via `listTaskComments()`
- State hydration via `StateHydrator`
- Fetches last 10 comments on initial load (sorted asc for display)
2. **TanStack Virtual**
- Version: 3.13.18
- Location: `task-chat-panel.tsx` (line 256)
- Current config: `estimateSize: 96`, `overscan: 6`
- Auto-scroll to bottom on new messages
3. **HTTP API Endpoint**
- Backend: `GET /api/v1/tasks/:id/comments` (limit/before/after/sort)
- Frontend: `listTaskComments(taskId, params)` in `lib/http/index.ts`
- Response: `{ comments, total, has_more, cursor }`
4. **Basic Tool Call Rendering**
- Expandable tool call cards
- Tool icons and status indicators
- Args/result display
### ❌ Still Missing (Needs Implementation)
1. **Rich Content Display**
- Basic thinking/todos/diff blocks rendered when metadata exists
- Error/status/progress cards added
- Inline diff viewer now supported for structured metadata
- Remaining: tool call enhancements, error card details, thinking toggle, diff extraction from tool results
4. **CodeMirror Input**
- Still using basic Textarea
- No markdown mode or syntax highlighting
- No command/mention autocomplete
5. **Rich Content Display**
- No thinking message display
- No todo list visualization
- No inline diff viewer
- Basic error display only
---
## Implementation Phases
### Phase 1: Pagination API + Lazy Loading (Week 1)
#### 1.1 Backend API Enhancements
**Update Existing Endpoint:**
```go
// File: apps/backend/internal/task/dto/requests.go
type ListCommentsRequest struct {
TaskID string
Limit int // default 10, max 100
Before string // comment ID for pagination (older)
After string // comment ID for newer messages
Sort string // "asc" | "desc", default "desc"
}
// File: apps/backend/internal/task/dto/dto.go
type ListCommentsResponse struct {
Comments []*v1.Comment `json:"comments"`
Total int `json:"total"`
HasMore bool `json:"has_more"` // NEW
Cursor string `json:"cursor"` // NEW
}
```
**Implementation Steps:**
1. Update `ListCommentsRequest` DTO to accept query params
2. Modify `CommentController.ListComments()` to pass pagination params
3. Update `Service.ListComments()` to support cursor-based queries
4. Modify SQLite repository query with `WHERE created_at < ?` and `LIMIT`
5. Calculate `has_more` by checking if count > limit
6. Return cursor as the last comment's ID
**Handler Update:**
```go
// File: apps/backend/internal/task/handlers/comment_handlers.go
func (h *CommentHandlers) httpListComments(c *gin.Context) {
taskID := c.Param("id")
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
before := c.Query("before")
sort := c.DefaultQuery("sort", "desc")
resp, err := h.commentController.ListComments(c.Request.Context(), dto.ListCommentsRequest{
TaskID: taskID,
Limit: limit,
Before: before,
Sort: sort,
})
// ... return resp with has_more and cursor
}
```
**Index Check:**
```sql
-- Should already exist from schema
CREATE INDEX IF NOT EXISTS idx_comments_task_created
ON task_comments(task_id, created_at DESC);
```
#### 1.2 Update SSR to Limit to 10
**Server Component:** `app/task/[id]/page.tsx`
```typescript
// Update to only fetch last 10 messages
comments = await listTaskComments(id, { limit: 10 }, { cache: 'no-store' });
```
**Frontend HTTP Client:** `lib/http/index.ts`
```typescript
export async function listTaskComments(
taskId: string,
params?: { limit?: number; before?: string; sort?: string },
options?: ApiRequestOptions
) {
const query = new URLSearchParams();
if (params?.limit) query.set('limit', params.limit.toString());
if (params?.before) query.set('before', params.before);
if (params?.sort) query.set('sort', params.sort);
const url = `/api/v1/tasks/${taskId}/comments${query.toString() ? `?${query}` : ''}`;
return fetchJson<ListCommentsResponse>(url, options);
}
```
**Types Update:** `lib/types/http.ts`
```typescript
export interface ListCommentsResponse {
comments: Comment[];
total: number;
has_more: boolean; // NEW
cursor: string; // NEW
}
```
#### 1.3 Store Enhancements
**Zustand Store:**
```typescript
type CommentsState = {
taskId: string | null;
items: Comment[]; // Sorted by created_at ASC
isLoading: boolean;
hasMore: boolean;
oldestCursor: string | null; // For lazy loading
// Actions
setComments(taskId: string, comments: Comment[]);
prependComments(comments: Comment[]); // For lazy load
addComment(comment: Comment); // For new messages
setCommentsMetadata(meta: { hasMore?, isLoading? });
};
```
#### 1.4 Lazy Loading Hook
**`useLazyLoadComments.ts`:**
```typescript
export function useLazyLoadComments(taskId: string) {
const { hasMore, oldestCursor, isLoading } = useStore(s => s.comments);
const prependComments = useStore(s => s.prependComments);
const loadMore = useCallback(async () => {
if (!hasMore || isLoading) return;
setCommentsMetadata({ isLoading: true });
const response = await fetch(
`/api/tasks/${taskId}/comments?limit=20&before=${oldestCursor}`
);
const { comments, has_more, cursor } = await response.json();
prependComments(comments);
setCommentsMetadata({
hasMore: has_more,
oldestCursor: cursor,
isLoading: false
});
}, [taskId, hasMore, oldestCursor, isLoading]);
return { loadMore, hasMore, isLoading };
}
```
---
### Phase 2: Smart Auto-Scroll + Lazy Load Detection (Week 1)
#### 2.1 Enhance Existing Virtual List
**Update:** `task-chat-panel.tsx` (already has TanStack Virtual at line 256)
**Add Lazy Load Detection:**
```typescript
// In TaskChatPanel component, after virtualizer setup (line 261)
const { loadMore, hasMore, isLoading } = useLazyLoadComments(taskId);
// Detect scroll to top for lazy loading
useEffect(() => {
const [firstItem] = virtualizer.getVirtualItems();
if (!firstItem) return;
// If first visible item is index 0 and we have more
if (firstItem.index === 0 && hasMore && !isLoading) {
loadMore();
}
}, [virtualizer.getVirtualItems(), hasMore, isLoading]);
// Show loading indicator at top when loading more
const showLoadingIndicator = isLoading && hasMore;
```
#### 2.2 Smart Auto-Scroll
**Replace Existing Auto-Scroll Logic** (currently at line 263-267 in `task-chat-panel.tsx`):
```typescript
// Add ref to track if user was at bottom
const wasAtBottomRef = useRef(true);
// Check if at bottom on scroll
const checkAtBottom = useCallback(() => {
const element = messagesContainerRef.current;
if (!element) return;
const { scrollTop, scrollHeight, clientHeight } = element;
wasAtBottomRef.current = scrollHeight - scrollTop - clientHeight < 50;
}, []);
// Add scroll listener
useEffect(() => {
const element = messagesContainerRef.current;
if (!element) return;
element.addEventListener('scroll', checkAtBottom);
return () => element.removeEventListener('scroll', checkAtBottom);
}, [checkAtBottom]);
// REPLACE existing auto-scroll effect (line 264-267)
// OLD: Always scrolls to bottom
// NEW: Only scroll if user was at bottom
useEffect(() => {
if (itemCount === 0) return;
if (wasAtBottomRef.current) {
virtualizer.scrollToIndex(itemCount - 1, {
align: 'end',
behavior: 'smooth',
});
}
}, [itemCount, virtualizer]);
```
#### 2.3 Maintain Scroll Position on Prepend
**Strategy:**
```typescript
// Before prepending new messages
const scrollElement = parentRef.current;
const oldScrollHeight = scrollElement.scrollHeight;
const oldScrollTop = scrollElement.scrollTop;
// Prepend messages to store
prependComments(newComments);
// After render, restore scroll position
requestAnimationFrame(() => {
const newScrollHeight = scrollElement.scrollHeight;
scrollElement.scrollTop = oldScrollTop + (newScrollHeight - oldScrollHeight);
});
```
---
### Phase 3: Component Architecture (Week 1-2)
#### 3.1 Component Hierarchy
```
TaskChatPanel (refactored)
├── TaskChatHeader
│ ├── Agent selector
│ ├── Settings (thinking, diff view)
│ └── Loading indicator
├── TaskChatMessages (TanStack Virtual)
│ ├── Lazy load indicator (top)
│ └── MessageRenderer (type router)
│ ├── UserMessage
│ ├── AssistantMessage + ToolCallList
│ ├── ThinkingMessage
│ ├── ToolCallCard (recursive for sub-agents)
│ ├── TodoListCard
│ ├── DiffCard
│ ├── ErrorCard
│ └── SystemMessage
└── TaskChatInput (CodeMirror with commands)
```
#### 3.2 Message Components
**Key Components:**
1. **MessageRenderer** - Type router based on `comment.type`
2. **UserMessage** - Avatar, timestamp, markdown content
3. **AssistantMessage** - Avatar, duration, tokens, copy button, markdown + tool calls
4. **ToolCallCard** - Collapsible, status badge, nested sub-agents, specialized renderers
5. **TodoListCard** - Progress bar, status icons, animated updates
6. **DiffCard** - File stats, expandable, uses @git-diff-view/react
7. **ErrorCard** - Error icon, message, stack trace
8. **ThinkingMessage** - Italic, bordered, toggle visibility
9. **SystemMessage** - Session info, warnings, status updates
**Shared Components:**
- `MessageHeader` - Avatar, name, timestamp, metadata
- `CopyButton` - Copy message content
- `ExpandButton` - Expand/collapse long content
- `StatusBadge` - Status indicators with animations
#### 3.3 State Management
**Expansion State (Zustand):**
```typescript
type UIState = {
expandedTools: Set<string>;
expandedMessages: Set<string>;
showThinking: boolean;
diffViewMode: 'unified' | 'split';
toggleToolExpanded(id: string): void;
toggleMessageExpanded(id: string): void;
};
```
**Persistence:**
- Store expansion state in localStorage via Zustand persist
- Restore on mount
- Clear on task change
---
### Phase 4: CodeMirror Input with Commands (Week 2)
#### 4.1 CodeMirror Setup
**Chat Input Configuration:**
```typescript
import { EditorView, basicSetup } from '@codemirror/basic-setup';
import { markdown } from '@codemirror/lang-markdown';
import { autocompletion } from '@codemirror/autocomplete';
import { keymap } from '@codemirror/view';
const chatExtensions = [
basicSetup({ lineNumbers: false }), // Minimal for chat
markdown(),
autocompletion({ override: [commandCompletions, mentionCompletions] }),
keymap.of([{ key: "Mod-Enter", run: handleSubmit }]),
EditorView.theme({
"&": { maxHeight: "300px", minHeight: "60px" },
".cm-content": { padding: "12px" },
".cm-scroller": { overflow: "auto" }
})
];
```
**Features:**
- Markdown mode with syntax awareness
- Slash commands (/read, /edit, /bash, etc.)
- File mentions (@filename.ts)
- Syntax highlighting for inline code
- Cmd+Enter to submit
- Auto-grow up to 300px height
#### 4.2 Command Autocomplete
**Command Completion Source:**
```typescript
import { CompletionContext } from '@codemirror/autocomplete';
const COMMANDS = [
{ label: '/read', detail: 'Read a file', info: 'Usage: /read <file_path>' },
{ label: '/edit', detail: 'Edit a file', info: 'Usage: /edit <file_path>' },
{ label: '/bash', detail: 'Run bash command', info: 'Usage: /bash <command>' },
{ label: '/grep', detail: 'Search in files', info: 'Usage: /grep <pattern>' },
{ label: '/help', detail: 'Show commands', info: 'Display all available commands' },
];
function commandCompletions(context: CompletionContext) {
const word = context.matchBefore(/\/\w*/);
if (!word || (word.from === word.to && !context.explicit)) return null;
return {
from: word.from,
options: COMMANDS.map(cmd => ({
label: cmd.label,
detail: cmd.detail,
info: cmd.info,
apply: cmd.label + ' ', // Add space after command
}))
};
}
```
**File Mention Completion:**
```typescript
function mentionCompletions(context: CompletionContext) {
const word = context.matchBefore(/@[\w\/\-\.]*/);
if (!word) return null;
// Get available files from git status or file tree
const files = getChangedFiles(); // From store or context
return {
from: word.from,
options: files.map(file => ({
label: `@${file.path}`,
detail: file.type,
info: `Reference ${file.path}`,
apply: `@${file.path}`,
}))
};
}
```
**Keyboard Navigation:**
- Built-in: Arrow up/down, Enter, Escape
- Tab to accept completion
- Fuzzy matching included
#### 4.3 Submit Handler
**Extract Content and Send:**
```typescript
function handleSubmit(view: EditorView): boolean {
const content = view.state.doc.toString();
if (!content.trim()) return false;
// Parse commands (optional - backend can also parse)
const commands = extractCommands(content);
// Send to backend
client.request('comment.add', {
task_id: taskId,
content,
metadata: commands.length > 0 ? { commands } : undefined
});
// Clear editor
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: '' }
});
return true;
}
```
#### 4.4 File Editing Integration
**Unified Editor for Multiple Contexts:**
**1. Chat Input (Minimal):**
```typescript
const chatExtensions = [
basicSetup({ lineNumbers: false }),
markdown(),
autocompletion({ override: [commandCompletions, mentionCompletions] }),
];
```
**2. File Viewer (Read-only):**
```typescript
const viewerExtensions = [
basicSetup(),
EditorView.editable.of(false),
EditorState.readOnly.of(true),
languageForFile(filename),
syntaxHighlighting(oneDarkHighlightStyle),
];
```
**3. File Editor (Full-featured):**
```typescript
const editorExtensions = [
basicSetup(),
languageForFile(filename),
syntaxHighlighting(oneDarkHighlightStyle),
lintGutter(),
autocompletion(),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
debouncedSave(update.state.doc.toString());
}
}),
];
```
**Language Detection:**
```typescript
function languageForFile(filename: string) {
const ext = filename.split('.').pop();
switch (ext) {
case 'js':
case 'jsx':
case 'ts':
case 'tsx':
return javascript({ jsx: true, typescript: ext.includes('ts') });
case 'py':
return python();
case 'go':
return go();
case 'md':
return markdown();
// ... 100+ languages supported
default:
return [];
}
}
```
**Shared Configuration:**
```typescript
// Reusable theme and settings
export const baseTheme = EditorView.theme({
"&": { height: "100%" },
".cm-scroller": { fontFamily: "var(--font-mono)" },
".cm-gutters": { backgroundColor: "var(--muted)" },
});
export const baseExtensions = [
baseTheme,
EditorState.tabSize.of(2),
EditorState.allowMultipleSelections.of(true),
];
```
---
### Phase 5: Rich Content Rendering (Week 2-3)
#### 5.1 Tool Call Enhancements
**Features:**
- Recursive rendering of sub-agent calls (Task tool)
- Tool-specific summaries (Read: filename, Bash: command preview)
- Status badges with animations (pending, running, success, error)
- Collapsible params and results
- Copy button for results
- Specialized renderers (Bash output with ANSI colors, JSON pretty-print)
**Metadata Structure:**
```typescript
type ToolCallMetadata = {
tool: {
id: string;
name: string;
args: Record<string, any>;
result: string | null;
status: 'pending' | 'running' | 'success' | 'error';
duration_ms?: number;
sub_agent_calls?: ToolCallMetadata[];
};
};
```
#### 5.2 Todo List Visualization
**Features:**
- Progress bar (completed / total)
- Status icons (Circle → Loader → CheckCircle)
- Active form vs content based on status
- Animated transitions
- Group by status (optional)
**Backend Integration:**
- Agent sends `progress` type comments
- Metadata contains todos array
- Updates sent as new comments (immutable)
- Frontend shows latest state
#### 5.3 Diff Integration
**Features:**
- Extract diffs from tool call results (Edit, Write tools)
- Show file stats (additions, deletions)
- Expandable diff viewer using @git-diff-view/react
- Syntax highlighting per language
- "Open in Changes" button → navigate to Changes tab
**Diff Sources:**
1. Tool result with `diff` field
2. Tool result with `old_content` and `new_content`
3. Unified diff string in result
#### 5.4 Thinking Display
**Features:**
- Toggle via settings
- Italic text with left border
- Markdown rendering
- Collapsed by default for long thoughts
- Visual distinction from regular messages
**Backend:**
- Agent sends `thinking` type comments
- Or `metadata.thinking` in content comments
#### 5.5 Error Handling
**Features:**
- Error cards with red border
- Error icon and title
- Error message
- Collapsible stack trace
- Retry button (if applicable)
- Copy error details
---
### Phase 6: Performance & Polish (Week 3-4)
#### 6.1 Performance Optimizations
**Memoization:**
```typescript
// Memoize message components
const MessageItem = memo(MessageRenderer, (prev, next) => {
return prev.comment.id === next.comment.id &&
prev.comment.updated_at === next.comment.updated_at;
});
// Memoize filtered comments
const visibleComments = useMemo(() => {
return comments.filter(c => {
if (c.type === 'thinking' && !showThinking) return false;
return true;
});
}, [comments, showThinking]);
```
**Debouncing:**
- Scroll position checks: 100ms
- Lazy load triggers: 200ms
- Auto-scroll: 50ms
- Input changes: 300ms (for autocomplete)
**Code Splitting:**
```typescript
// Lazy load heavy components
const MonacoDiffEditor = lazy(() => import('./MonacoDiffEditor'));
const MermaidRenderer = lazy(() => import('./MermaidRenderer'));
```
**Bundle Size:**
- Target: ~150KB increase (CodeMirror core + essential languages)
- Use tree-shaking
- Import only needed language modes
- Lazy load additional languages on demand
#### 6.2 UX Polish
**Interactions:**
- Smooth height transitions (CSS transitions)
- Hover states on interactive elements
- Loading skeletons for lazy load
- Copy feedback (checkmark animation)
- Keyboard shortcuts (j/k navigation, / for command palette)
**Visual:**
- Consistent spacing and alignment
- Color-coded message types
- Status indicators with meaningful colors
- Avatar fallbacks with initials
- Timestamp formatting (relative times)
- Duration formatting (ms, s, m)
**Accessibility:**
- Keyboard navigation
- Focus management in autocomplete
#### 6.3 Error Boundaries
**Wrap Critical Components:**
```typescript
<ErrorBoundary
fallback={<ErrorFallback />}
onError={(error) => console.error('Message render error:', error)}
>
<MessageRenderer comment={comment} />
</ErrorBoundary>
```
**Graceful Degradation:**
- If markdown fails, show plain text
- If diff parsing fails, show unified diff string
- If tool metadata is invalid, show raw JSON
---
## Backend Changes
### API Enhancements
**New Endpoints:**
```
GET /api/tasks/:id/comments?limit=10&before=:cursor&sort=desc
POST /api/tasks/:id/comments (existing, no changes)
```
**Response Format:**
```json
{
"comments": [
{
"id": "cmt_123",
"task_id": "tsk_456",
"author_type": "agent",
"content": "...",
"type": "content",
"metadata": {...},
"created_at": "2025-01-14T12:00:00Z"
}
],
"has_more": true,
"cursor": "cmt_100"
}
```
**Indexes:**
```sql
CREATE INDEX IF NOT EXISTS idx_comments_task_created
ON task_comments(task_id, created_at DESC);
```
### WebSocket (No Changes)
Existing `comment.added` notification works as-is.
### Comment Type Usage
**Agent Integration:**
- `message` - User messages
- `content` - Agent text responses
- `tool_call` - Tool executions
- `progress` - Todo list updates
- `thinking` - Agent reasoning
- `error` - Error messages
- `status` - System notifications
---
## File Structure
```
apps/web/
├── app/task/[id]/
│ ├── page.tsx (SSR: fetch initial comments)
│ └── page-client.tsx (Client: hydrate + subscribe)
├── components/task/
│ ├── task-chat-panel.tsx (Main container, refactored)
│ └── chat/
│ ├── TaskChatHeader.tsx
│ ├── TaskChatMessages.tsx (TanStack Virtual)
│ ├── TaskChatInput.tsx (Lexical + commands)
│ ├── messages/
│ │ ├── MessageRenderer.tsx
│ │ ├── UserMessage.tsx
│ │ ├── AssistantMessage.tsx
│ │ ├── ThinkingMessage.tsx
│ │ ├── SystemMessage.tsx
│ │ └── ErrorCard.tsx
│ ├── tools/
│ │ ├── ToolCallCard.tsx
│ │ ├── ToolStatusBadge.tsx
│ │ └── ToolIcon.tsx
│ ├── content/
│ │ ├── TodoListCard.tsx
│ │ ├── TodoItem.tsx
│ │ ├── DiffCard.tsx
│ │ └── CopyButton.tsx
│ ├── input/
│ │ ├── CodeMirrorEditor.tsx
│ │ ├── commandCompletions.ts
│ │ ├── mentionCompletions.ts
│ │ └── editorExtensions.ts
│ └── hooks/
│ ├── useLazyLoadComments.ts
│ ├── useSmartAutoScroll.ts
│ └── useChatVirtualizer.ts
└── lib/
├── state/store.ts (Enhanced with pagination)
└── api/comments.ts (New: fetch comments API)
apps/backend/
└── internal/task/
├── handlers/
│ └── comment_handlers.go (Add pagination support)
└── repository/
└── sqlite.go (Cursor-based queries)
```
**Total new files:** ~25
**Modified files:** 4 (page.tsx, page-client.tsx, task-chat-panel.tsx, store.ts, comment_handlers.go, sqlite.go)
---
## Implementation Timeline
### Week 1: Pagination & Smart Scroll
- [x] ~~SSR setup~~ (Already done)
- [x] ~~TanStack Virtual~~ (Already done v3.13.18)
- [x] ~~Basic tool calls~~ (Already done)
- [ ] Backend pagination API (limit, before, cursor)
- [ ] Update SSR to limit to 10 messages
- [ ] Lazy loading on scroll up
- [ ] Smart auto-scroll (only if at bottom)
- [ ] Scroll position preservation on prepend
### Week 2: Rich Content
- [ ] Enhanced message components (copy, timestamps)
- [ ] Nested tool call rendering (sub-agents)
- [ ] Todo list visualization with progress bars
- [ ] Inline diff viewer integration
- [ ] Thinking message display
- [ ] Error card enhancements
### Week 3: CodeMirror Input
- [ ] Replace Textarea with CodeMirror
- [ ] Markdown mode with syntax highlighting
- [ ] Slash command autocomplete (/read, /edit, /bash)
- [ ] File mention autocomplete (@file.ts)
- [ ] Keyboard navigation (arrows, enter, escape)
- [ ] Command parsing & execution
- [ ] File viewer/editor config (bonus)
### Week 4: Polish & Testing
- [ ] Performance optimization (memoization audit)
- [ ] Error boundaries for message rendering
- [ ] Accessibility (keyboard nav)
- [ ] Documentation updates
---