--- type: decision adr: 005 status: accepted date: 2026-10-17 supersedes: tags: [adr, async, tokio, threads] --- # ADR-005: adopt async gradually — sync core, then threads, then tokio ## Context Ranger's fatal flaw is a synchronous loop that stats every file inline ([[research/ranger-deep-dive]]). yazi proves tokio solves it but is a lot of machinery to learn while also learning Rust. lf (Go goroutines) shows threads alone already keep a UI responsive. ## Options considered | Option | Pros | Cons | |--------|------|------| | Gradual: sync → std threads + channels → tokio | learnable steps; working product at every stage | one refactor of the preview pipeline in Sprint 8 | | tokio from day 1 | final architecture sooner | async lifetimes + borrow checker + TUI at once = frustration barrier | | threads-only forever | simpler mental model | ecosystem (notify bridges, timeouts, process spawning) is tokio-first | ## Decision - **Sprints 1–5**: synchronous core. The TUI loop owns all state; directory loading and file ops are fast enough synchronously for a first product. - **Sprints 6–7**: previews move to `std::thread` + `std::sync::mpsc` — UI stays responsive, ownership lessons are explicit. - **Sprint 8**: adopt **tokio** for the full preview/media pipeline (yazi architecture: runtime workers marshal results to the single-threaded UI actor via a channel). Introduce cancellation via a generation-counter ("ticket") pattern before adopting tokio `abort`. ## Consequences - Positive: each concurrency model is learned in isolation; no big-bang async rewrite of the UI. - Negative: preview pipeline is written twice (thread → task) — accepted as a deliberate learning repetition (Kaufman: quantity of practice beats premature perfection). - Mitigations: keep the preview pipeline behind a small interface (`Previewer` trait, ADR-002) so the swap is local, not global.