A Rust terminal UI for reviewing code changes made by coding agents. Features vim keybindings, side-by-side diffs, file/line comments, and markdown output for feeding instructions back to agents.
- Input: Git working tree changes (
git diff HEAD) - Comments: Both file-level and line-level
- File Status: Binary (reviewed / not reviewed)
- Output: Structured Markdown optimized for LLM consumption
- Navigation: Vim keybindings
- Diff View: Side-by-side (primary)
- Persistence: JSON files on disk
[dependencies]
ratatui = "0.29" # TUI framework
crossterm = "0.29" # Terminal backend
git2 = "0.19" # Git operations
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
thiserror = "2.0"
anyhow = "1.0"
directories = "5.0" # XDG paths
unicode-width = "0.2"
uuid = { version = "1.0", features = ["v4"] }src/
├── main.rs # Entry point, CLI args, main loop
├── app.rs # Application state
├── error.rs # Error types
│
├── git/
│ ├── mod.rs
│ ├── diff.rs # Diff parsing via git2
│ └── repository.rs # Repo discovery
│
├── model/
│ ├── mod.rs
│ ├── review.rs # ReviewSession, FileReview
│ ├── comment.rs # Comment, CommentType
│ └── diff_types.rs # DiffFile, DiffHunk, DiffLine
│
├── ui/
│ ├── mod.rs
│ ├── app_layout.rs # Main layout
│ ├── file_list.rs # File list panel
│ ├── diff_view.rs # Side-by-side diff
│ ├── comment_panel.rs # Comments display/input
│ ├── status_bar.rs # Header and status
│ ├── help_popup.rs # Keybinding help
│ └── styles.rs # Colors and styling
│
├── input/
│ ├── mod.rs
│ ├── handler.rs # Event processing
│ ├── keybindings.rs # Key -> Action mapping
│ └── mode.rs # Normal, Comment, Search modes
│
├── persistence/
│ ├── mod.rs
│ └── storage.rs # Save/load sessions
│
└── output/
├── mod.rs
└── markdown.rs # Export to markdown
pub struct ReviewSession {
pub id: String,
pub repo_path: PathBuf,
pub base_commit: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub files: HashMap<PathBuf, FileReview>,
pub session_notes: Option<String>,
}pub struct FileReview {
pub path: PathBuf,
pub reviewed: bool,
pub file_comments: Vec<Comment>,
pub line_comments: HashMap<u32, Vec<Comment>>,
pub status: FileStatus, // Added, Modified, Deleted, Renamed
}pub struct Comment {
pub id: String,
pub content: String,
pub comment_type: CommentType, // Note, Suggestion, Issue, Praise
pub created_at: DateTime<Utc>,
pub line_context: Option<LineContext>,
}+------------------------------------------------------------------+
| tuicr - Code Review [branch: main] |
+------------------+-----------------------------------------------+
| FILES | ═══ src/main.rs [M] ══════════════════════ |
| [x] src/main.rs | OLD (HEAD) │ NEW (working) |
| [ ] src/lib.rs | 1 fn main() { │ 1 fn main() { |
| ▶[ ] src/app.rs | 2 let x = 1; │ 2 let x = 42; |
| | 3 } │ 3 } |
| | ─────────────────────────────────────────────|
| | 💬 Line 2: [ISSUE] Magic number... |
| | ═══ src/lib.rs [M] ═══════════════════════ |
| | OLD (HEAD) │ NEW (working) |
| | 1 pub fn foo() {} │ 1 pub fn foo() { |
| | │ 2 bar(); |
| | │ 3 } |
+------------------+-----------------------------------------------+
| [N]ormal j/k:scroll Enter:jump r:reviewed c:comment ?:help |
+------------------------------------------------------------------+
-
Diff panel: Shows ALL changed files in one continuous scroll
- Each file has a header separator (
═══ filename [status] ═══) - Side-by-side diff for each file
- Inline comments displayed below relevant lines
- Scroll through entire review with
j/korCtrl-d/u
- Each file has a header separator (
-
File list panel: Quick navigation aid (not selection)
- Shows review status
[x]/[ ]for each file ▶indicator shows which file is currently visible in diffEnteron file list jumps to that file in the scroll- File list auto-highlights based on scroll position
- Shows review status
-
Layout proportions:
- File list: 20% width (collapsible with
h) - Diff view: 80% width (full width when file list collapsed)
- File list: 20% width (collapsible with
| Key | Action |
|---|---|
j/k |
Scroll diff down/up one line |
Ctrl-d/u |
Half page down/up |
Ctrl-f/b |
Full page down/up |
gg/G |
Go to first/last file |
{/} |
Jump to previous/next file header |
[c/]c |
Jump to previous/next comment |
[[/]] |
Jump to previous/next hunk |
| Key | Action |
|---|---|
Tab |
Toggle focus between file list and diff |
Enter |
Jump to selected file in diff (when in file list) |
h |
Collapse file list panel |
l |
Expand file list panel / focus diff |
| Key | Action |
|---|---|
r |
Toggle current file reviewed |
c |
Add line comment at cursor |
C |
Add file comment for current file |
e |
Edit comment under cursor |
dd |
Delete comment under cursor |
1-4 |
Set comment type (1=Note, 2=Suggestion, 3=Issue, 4=Praise) |
| Key | Action |
|---|---|
:w |
Save session |
:e |
Reload diff files |
:clip (:export) |
Export markdown |
:q |
Quit |
:wq / ZZ |
Save and quit |
? |
Show help |
Location: ~/.local/share/tuicr/reviews/ (XDG compliant)
Filename: {repo}_{commit}_{timestamp}.json
Auto-saves every 60 seconds and on file navigation.
# Code Review: {repo_name}
**Reviewed:** {timestamp}
**Base Commit:** `{commit_sha}`
**Files Reviewed:** {reviewed}/{total}
## Summary
{session_notes}
## Files
### M `src/auth.rs` [REVIEWED]
#### File Comments
> **[SUGGESTION]** Consider adding unit tests
#### Line Comments
```rust
// Line 42
let expiry = now + 3600;[ISSUE] Magic number should be a named constant
src/auth.rs:42 - Magic number should be a named constantsrc/auth.rs:15 - Use?operator instead of unwrap
---
## Implementation Order
### Phase 0: Overview
1. Setup README.md
2. Setup AGENTS.md
### Phase 1: Foundation
1. Set up Cargo.toml with dependencies
2. Create module structure (empty mod.rs files)
3. Implement `model/` structs with serde derives
4. Implement `error.rs` with thiserror
### Phase 2: Git Integration
5. Implement `git/repository.rs` - repo discovery
6. Implement `git/diff.rs` - parse `git diff HEAD` into DiffFile structs
### Phase 3: Persistence
7. Implement `persistence/storage.rs` - save/load JSON sessions
### Phase 4: TUI Core
8. Implement `app.rs` - application state container
9. Implement `input/mode.rs` and `input/keybindings.rs`
10. Implement `ui/styles.rs` - color definitions
11. Implement `ui/status_bar.rs` - header and footer
### Phase 5: TUI Widgets
12. Implement `ui/file_list.rs` - file list panel
13. Implement `ui/diff_view.rs` - side-by-side diff (most complex)
14. Implement `ui/comment_panel.rs` - comment display/input
15. Implement `ui/help_popup.rs` - keybinding overlay
16. Implement `ui/app_layout.rs` - compose all widgets
### Phase 6: Input Handling
17. Implement `input/handler.rs` - event loop and action dispatch
### Phase 7: Output
18. Implement `output/markdown.rs` - export functionality
### Phase 8: Main
19. Implement `main.rs` - CLI args, startup, main loop, shutdown
---
## Verification Plan
1. **Build**: `cargo build` should compile without errors
2. **Format**: `cargo fmt` after each file modification
3. **Manual test flow**:
- Create a test git repo with multiple staged/unstaged changed files
- Run `tuicr` in that directory
- Scroll through all diffs with `j/k` and `Ctrl-d/u`
- Verify file list `▶` indicator updates as you scroll
- Use `{/}` to jump between file headers
- Press `Tab` to focus file list, use `Enter` to jump to a file
- Add line comments with `c`, file comments with `C`
- Mark files reviewed with `r`
- Save with `:w`, verify JSON file created
- Export with `:clip`, verify markdown output
- Quit with `:q`, restart, verify session loads with scroll position
---
## Key Files to Create
| File | Purpose |
|------|---------|
| `src/model/review.rs` | Core domain types |
| `src/git/diff.rs` | Git diff parsing |
| `src/ui/diff_view.rs` | Infinite-scroll side-by-side diff (all files in one view) |
| `src/input/handler.rs` | Vim keybinding implementation |
| `src/output/markdown.rs` | LLM-optimized output |