Add mode switching.
This commit is contained in:
parent
5d213b43d3
commit
5cb6647513
3 changed files with 394 additions and 86 deletions
102
PLAN.md
102
PLAN.md
|
|
@ -1,77 +1,40 @@
|
|||
# Implementation Plan
|
||||
|
||||
## Phase 1: Minimal Conversation Loop
|
||||
|
||||
**Done when:** Multi-turn streaming conversation with Claude works in terminal
|
||||
|
||||
### 1.1 Project Scaffolding
|
||||
- `Cargo.toml` with initial dependencies:
|
||||
- `ratatui`, `crossterm` — TUI
|
||||
- `tokio` (full features) — async runtime
|
||||
- `serde`, `serde_json` — serialization
|
||||
- `thiserror` — error types
|
||||
- `tracing`, `tracing-subscriber` — structured logging
|
||||
- `reqwest` (with `stream` feature) — HTTP client for SSE
|
||||
- `futures` — stream combinators
|
||||
- Establish `src/{app,tui,core,provider}/mod.rs` stubs
|
||||
- `cargo build` passes; `cargo clippy -- -D warnings` passes on empty stubs
|
||||
|
||||
### 1.2 Shared Types (`src/core/types.rs`)
|
||||
- `StreamEvent` enum: `TextDelta(String)`, `InputTokens(u32)`, `OutputTokens(u32)`, `Done`, `Error(String)`
|
||||
- `UserAction` enum (TUI → core channel): `SendMessage(String)`, `Quit`
|
||||
- `UIEvent` enum (core → TUI channel): `StreamDelta(String)`, `TurnComplete`, `Error(String)`
|
||||
- `ConversationMessage` struct: `role: Role`, `content: String`
|
||||
- All types derive `Debug`; all public types have doc comments
|
||||
|
||||
### 1.3 Provider: `ModelProvider` Trait + Claude SSE (`src/provider/`)
|
||||
- `ModelProvider` trait: `async fn stream(&self, messages: &[ConversationMessage]) -> impl Stream<Item = StreamEvent>`
|
||||
- `ClaudeProvider` struct: API key from env, `reqwest` HTTP client
|
||||
- Serialize messages to Anthropic Messages API JSON format
|
||||
- Parse SSE byte stream → `StreamEvent` (handle `content_block_delta`, `message_delta` for tokens, `message_stop`)
|
||||
- Unit tests: SSE parsing from hardcoded byte fixtures in `#[cfg(test)]`
|
||||
|
||||
### 1.4 Core: Conversation State + Orchestrator Loop (`src/core/`)
|
||||
- `ConversationHistory`: `Vec<ConversationMessage>` with `push` and `messages()` (flat list, no tree yet)
|
||||
- `Orchestrator` struct holding history, provider, channel senders/receivers
|
||||
- Orchestrator loop:
|
||||
1. Await `UserAction` from TUI channel
|
||||
2. On `SendMessage`: append user message, call `provider.stream()`
|
||||
3. Forward each `StreamEvent` as `UIEvent` to TUI
|
||||
4. Accumulate deltas into assistant message; append to history on `Done`
|
||||
5. On `Quit`: break loop
|
||||
|
||||
### 1.5 TUI: Layout + Input + Streaming Display (`src/tui/`)
|
||||
- `AppState` struct: `messages: Vec<(Role, String)>`, `input: String`, `scroll: u16`
|
||||
- Ratatui layout: full-height `Paragraph` output area (scrollable) + single-line `Paragraph` input
|
||||
- Insert mode only — printable chars append to `input`, Enter sends `UserAction::SendMessage`, Backspace deletes
|
||||
- On `UIEvent::StreamDelta`: append to last assistant message in `messages`, re-render
|
||||
- On `UIEvent::TurnComplete`: finalize assistant message
|
||||
- Crossterm raw mode enter/exit; restore terminal on panic or clean exit
|
||||
|
||||
### 1.6 App Wiring + Entry Point (`src/app/`, `src/main.rs`)
|
||||
- `main.rs`: parse `--project-dir <path>` CLI arg
|
||||
- Initialize `tracing_subscriber` (log to file, not stdout — avoids TUI interference)
|
||||
- Create `tokio::sync::mpsc` channel pair for `UserAction` and `UIEvent`
|
||||
- Spawn `Orchestrator::run()` as a tokio task
|
||||
- Run TUI event loop on main thread (Ratatui requires main thread for crossterm)
|
||||
- On `UserAction::Quit` or Ctrl-C: signal orchestrator shutdown, restore terminal, exit cleanly
|
||||
|
||||
### 1.7 Phase 1 Unit Tests
|
||||
- Provider: SSE byte fixture → correct `StreamEvent` sequence
|
||||
- Provider: `ConversationMessage` vec → correct Anthropic API JSON shape
|
||||
- Core: `ConversationHistory` push/read roundtrip
|
||||
- Core: Orchestrator state transitions against mock `StreamEvent` sequence (no real API)
|
||||
|
||||
## Phase 2: Vim Modes and Navigation
|
||||
- Normal, Insert, Command modes with visual indicator
|
||||
- `j`/`k` scroll in Normal mode
|
||||
- `:quit`, `:clear` commands
|
||||
- **Done when:** Fluid mode switching and scrolling feels vim-native
|
||||
|
||||
**Done when:** Fluid mode switching and scrolling feels vim-native
|
||||
|
||||
### 2.3 Command Parser + Execution
|
||||
- Parse command buffer on enter: `:quit` -> `Quit`, `:clear` -> clear messages and history, `:q` alias for `:quit`
|
||||
- Return appropriate `LoopControl` or mutate `AppState` directly for display-only commands
|
||||
- Unknown commands -> show error in a transient status area
|
||||
|
||||
### 2.4 Status Bar + Command Overlay
|
||||
- Add a status bar row at the bottom of the layout (below input pane)
|
||||
- Status bar shows: current mode (`-- NORMAL --`, `-- INSERT --`), token totals (placeholder for now)
|
||||
- Style: bold, color-coded per mode (following vim convention)
|
||||
- Command input renders as a floating bar near the top of the screen (overlay on the output pane), not in the status bar
|
||||
- Command overlay: shows `:` prefix + partial command buffer, only visible in Command mode
|
||||
- Overlay dismisses on Esc or Enter
|
||||
|
||||
### 2.6 Input Pane Behavior by Mode
|
||||
- Normal mode: input pane shows last draft (unsent) message or is empty, not editable
|
||||
- Insert mode: input pane is editable (current behavior)
|
||||
- Command mode: floating overlay near top shows `:` prefix + command buffer; input pane unchanged
|
||||
- Cursor visibility: show cursor in Insert and Command modes, hide in Normal
|
||||
|
||||
### 2.7 Phase 2 Unit Tests
|
||||
- Mode transitions: Normal->Insert->Normal, Normal->Command->Normal, Command execute
|
||||
- Key dispatch: correct handler called per mode
|
||||
- Command parser: `:quit`, `:clear`, `:q`, unknown command
|
||||
- Scroll clamping: j/k at boundaries, G/gg
|
||||
- `insta` snapshot tests: mode indicator rendering for each mode, layout with status bar
|
||||
- Existing Phase 1 input tests still pass (insert mode behavior unchanged)
|
||||
|
||||
## Phase 3: Tool Execution
|
||||
- `Tool` trait, `ToolRegistry`, core tools (`read_file`, `write_file`, `shell_exec`)
|
||||
- Tool definitions in API requests, parse tool-use responses
|
||||
- Approval gate: core → TUI pending event → user approve/deny → result back
|
||||
- Approval gate: core -> TUI pending event -> user approve/deny -> result back
|
||||
- Working directory confinement + path validation (no Landlock yet)
|
||||
- **Done when:** Claude can read, modify files, and run commands with user approval
|
||||
|
||||
|
|
@ -81,8 +44,3 @@
|
|||
- `:net on/off` toggle, state in status bar
|
||||
- Graceful degradation on older kernels
|
||||
- **Done when:** Writes outside project dir fail; network toggle works
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue