Files
zesdex/docs/CODEMAPS/frontend.md
T

167 lines
7.4 KiB
Markdown
Raw Normal View History

# TUI (Terminal User Interface)
Dibangun di atas **ratatui** + **crossterm**. Kode ada di `apps/interfaces/tui/src/`.
## Struktur Source
```
apps/interfaces/tui/src/
├── run.rs # Event loop utama
├── state.rs # AppStateRest — single source of truth
├── action.rs # apply_action(): satu-satunya mutator state
├── turn.rs # Spawn agent turn di background thread
├── lib.rs # Re-export publik
├── controller/
│ ├── input.rs # Key handler → Vec<Action>
│ └── command.rs # Slash command parser
├── view/
│ ├── mod.rs # Layout + pre_render() + draw()
│ ├── chat.rs # Chat transcript panel (dengan display cache)
│ ├── sidebar.rs # Sidebar: workflow, tasks, usage
│ ├── status.rs # Status bar satu baris
│ ├── markdown.rs # Markdown → styled Span (pulldown-cmark)
│ ├── workflow.rs # Workflow/hive-mind progress panel
│ ├── theme.rs # Tokyo Night color palette (const)
│ └── overlays/ # 16 overlay panel
└── model/ # Data model lokal TUI
```
## Render Pipeline (Per Frame)
```
run_loop_inner() [50ms in-flight / 200ms idle]
├── drain expired toasts (1x, bukan 2x)
├── if dirty:
│ view::pre_render(&mut state) ← update cache (markdown, token count)
│ terminal.draw(|f| view::draw(f, &state))
│ state.dirty = false
└── poll events → apply_action → Action::Tick
```
### Optimasi Performa
| Masalah lama | Solusi saat ini |
|---|---|
| `count_tokens` (tiktoken) setiap frame | Cache `cached_token_count`, update hanya saat pesan baru |
| `render_markdown` ulang setiap frame | `display_lines_cache` di `AppStateRest`, rebuild saat `transcript_cache.dirty` |
| `Vec::remove(0)` untuk evict pesan lama | `VecDeque::pop_front()` — O(1) |
| `Mutex<bool>` untuk `turn_in_flight` | `Arc<AtomicBool>` — lock-free |
| Render terus meski idle | Skip `terminal.draw()` jika `dirty == false` |
| Poll 50ms konstan | Adaptif: 50ms saat in-flight, 200ms saat idle |
| `drain_expired_toasts` 2x per iterasi | Sekali saja di `run_loop_inner` |
## State (AppStateRest)
`AppStateRest` di `state.rs` adalah satu-satunya sumber kebenaran TUI:
```
AppStateRest {
settings: Settings // provider, model, dll
app_config: AppConfig // endpoint, env vars
workspace_roots: Vec<PathBuf> // working directories
session_dir / session_id // path sesi aktif
memory_dir // direktori memory
session_runtime: Option<SessionRuntime> // history pesan, usage stats
transcript_cache: TranscriptCache // VecDeque<ChatMessageDisplay>
scroll: ScrollState // offset scroll pane chat
input: InputState // buffer, cursor, history, autocomplete
misc: MiscState // overlay aktif, toasts, flags
turn_events: Arc<Mutex<VecDeque<TurnEvent>>> // queue event dari agent
turn_in_flight_flag: Arc<AtomicBool> // apakah agent sedang jalan
abort_flag: Arc<AtomicBool> // sinyal abort oleh user
// Cache performa
display_lines_cache: Vec<Line<'static>> // hasil render markdown
cached_token_count: usize // token count terkini
token_count_dirty: bool // perlu hitung ulang?
last_render_width: u16 // lebar terminal saat render terakhir
dirty: bool // perlu render ulang?
quit: bool // keluar dari loop?
}
```
**Aturan mutasi:**
- Dimutasi hanya dari `action.rs::apply_action()` dan `run.rs` (untuk dirty/quit)
- Semua fungsi `view/*` bersifat read-only terhadap state
- `pre_render_chat()` boleh mutasi hanya field cache (`display_lines_cache`, `cached_token_count`, `token_count_dirty`)
## Input & Actions
`controller/input.rs::handle_key()``Vec<Action>``apply_action(&mut state, action)`
Semua mutasi state melewati satu titik: `apply_action`. Controller tidak tahu *bagaimana* state diubah, hanya *action apa* yang dihasilkan.
### Action Utama
| Action | Efek |
|--------|------|
| `SubmitInput(text)` | Push ke transcript, spawn agent turn |
| `Tick` | Drain `TurnEvent` queue, update state dari hasil agent |
| `ScrollUp/Down` | Ubah `scroll.offset` |
| `OpenOverlay(v)` | Set `misc.overlay = v` |
| `Resize(w, h)` | Invalidasi cache display, set `last_render_width` |
| `AbortTurn` | Store `true` ke `abort_flag` |
| `ForceQuit` | Set `quit = true` |
## Overlays (16 Panel)
| Overlay | File | Fungsi |
|---------|------|--------|
| `Help` | `overlays/help.rs` | Daftar shortcut keyboard |
| `Settings` | `overlays/settings.rs` | Panel pengaturan |
| `Bash` | `overlays/bash.rs` | Background shell jobs |
| `QuitConfirm` | `overlays/quit_confirm.rs` | Konfirmasi keluar |
| `KeyInput` | `overlays/key_input.rs` | Capture key binding |
| `Editor` | `overlays/editor.rs` | File editor inline |
| `Effort` | `overlays/effort.rs` | Pilih level reasoning LLM |
| `Mcp` | `overlays/mcp.rs` | Manajemen MCP server |
| `Todo` | `overlays/todo.rs` | Daftar TODO |
| `Rewind` | `overlays/rewind.rs` | Navigasi history pesan |
| `Learning` | `overlays/learning.rs` | Viewer lesson |
| `Usage` | `overlays/usage.rs` | Statistik token |
| `Loading` | `overlays/loading.rs` | Spinner generik |
| `ModelSelector` | `overlays/model_selector.rs` | Pilih model LLM |
| `ClearConfirm` | `overlays/clear_confirm.rs` | Konfirmasi clear chat |
## Layout Terminal
```
┌───────────────────────────────────────────────┐
│ │
│ Chat Transcript Sidebar (≥90) │
│ (view/chat.rs) ┌────────────┐ │
│ VecDeque messages │ Workflow │ │
│ + markdown cache ├────────────┤ │
│ scrollable │ Tasks │ │
│ ├────────────┤ │
│ │ Usage │ │
│ └────────────┘ │
├───────────────────────────────────────────────┤
Input Bar + Autocomplete dropdown │
├───────────────────────────────────────────────┤
│ ⚡zesdex READY │ ...center... │ tok · model │
└───────────────────────────────────────────────┘
```
Sidebar hanya tampil jika lebar terminal ≥ 90 kolom.
## Theme
`view/theme.rs` mendefinisikan palette **Tokyo Night** sebagai `const Color`:
`PRIMARY`, `BG`, `SURFACE`, `SURFACE_ELEVATED`, `BORDER`, `TEXT`, `TEXT_DIM`, `TEXT_MUTED`, `SUCCESS`, `WARNING`, `ERROR`, `INFO`, `HIGHLIGHT`, `CODE_BG`, dll.
## Markdown Rendering
`view/markdown.rs::render_markdown(text, width, dim)`:
- Parse dengan `pulldown-cmark`
- Hasilkan `Vec<Span<'static>>` dengan styling
- Support: heading, code block, diff block (warna +/-/@@), list, blockquote, table, inline code, link
- `dim=true` → semua span memakai `TEXT_DIM` + italic (untuk tool output)
- Hasil di-cache di `AppStateRest::display_lines_cache`