From 23351b0962fb1d85ba805fb4c2b44ecb9e086486 Mon Sep 17 00:00:00 2001 From: Kanyin Cai Date: Sat, 21 Mar 2026 15:03:11 +0100 Subject: [PATCH] untrack .planning/ directory already covered by .gitignore --- .gitignore | 1 + .planning/PROJECT.md | 77 ---- .planning/ROADMAP.md | 62 --- .planning/codebase/ARCHITECTURE.md | 223 ---------- .planning/codebase/CONCERNS.md | 263 ----------- .planning/codebase/CONVENTIONS.md | 175 -------- .planning/codebase/INTEGRATIONS.md | 208 --------- .planning/codebase/STACK.md | 146 ------ .planning/codebase/STRUCTURE.md | 256 ----------- .planning/codebase/TESTING.md | 251 ----------- .../phases/01-implementation/01-01-PLAN.md | 417 ------------------ 11 files changed, 1 insertion(+), 2078 deletions(-) delete mode 100644 .planning/PROJECT.md delete mode 100644 .planning/ROADMAP.md delete mode 100644 .planning/codebase/ARCHITECTURE.md delete mode 100644 .planning/codebase/CONCERNS.md delete mode 100644 .planning/codebase/CONVENTIONS.md delete mode 100644 .planning/codebase/INTEGRATIONS.md delete mode 100644 .planning/codebase/STACK.md delete mode 100644 .planning/codebase/STRUCTURE.md delete mode 100644 .planning/codebase/TESTING.md delete mode 100644 .planning/phases/01-implementation/01-01-PLAN.md diff --git a/.gitignore b/.gitignore index 246b034d..18753525 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ result-* .vscode/ *.snap +.planning/ diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md deleted file mode 100644 index d95b4641..00000000 --- a/.planning/PROJECT.md +++ /dev/null @@ -1,77 +0,0 @@ -# Yazi Image Preview Quality Improvement - -## What This Is - -An improvement to yazi's image preview downscaling so that thumbnails displayed in the terminal preview pane are visibly sharper. The current pipeline uses the `image` crate's built-in `resize()` with configurable filter types, but the results appear noticeably less sharp than native image viewers like macOS Preview. - -## Core Value - -Image previews in yazi should look as sharp as the terminal's pixel resolution allows — downscaling artifacts should not be the bottleneck. - -## Requirements - -### Validated - -(None yet — ship to validate) - -### Active - -- [ ] Improve the downscaling/resizing algorithm in `yazi-adapter` so preview thumbnails are visibly sharper -- [ ] Maintain existing configurability (users can still select filter types) -- [ ] No regression in preview latency perceptible during normal navigation - -### Out of Scope - -- Full image pipeline audit (ICC, encoding, protocol drivers) — focus is the resize step only -- Color accuracy improvements — not the reported issue -- Sixel/Chafa-specific quality — user's workflow is iTerm2/WezTerm (IIP protocol) -- Adding new image protocol support - -## Context - -**Current implementation:** `yazi-adapter/src/image.rs` — `Image::downscale()` and `Image::precache()` both call `img.resize(w, h, Self::filter())` using the `image` crate's `DynamicImage::resize()`. The default filter is `lanczos3` (configured in `yazi-config/preset/yazi-default.toml`). - -**Why it's blurry:** The `image` crate's resize implementation, while functional, uses a simpler convolution approach compared to dedicated image processing libraries (e.g., `fast_image_resize`, `resize`). For downscaling large images to small preview sizes, the quality difference is noticeable — especially for fine detail and text in images. - -**Key files:** -- `yazi-adapter/src/image.rs` — resize logic, filter selection, decode pipeline -- `yazi-config/src/preview/preview.rs` — `image_filter` and `image_quality` config -- `yazi-config/preset/yazi-default.toml` — default settings (`image_filter = "lanczos3"`, `image_quality = 90`) -- `yazi-adapter/src/drivers/iip.rs` — iTerm2 Inline Protocol driver (user's protocol) - -**Brownfield context:** This is an existing, mature Rust project (1,363 commits, 15 workspace crates). The `yazi-adapter` crate has zero tests currently (noted in codebase concerns). The project uses Rust edition 2024 with MSRV 1.92.0. - -## Constraints - -- **Rust edition**: 2024, MSRV 1.92.0 -- **Dependencies**: Must be compatible with existing workspace dependency versions -- **Performance**: Preview generation must not add perceptible latency -- **Backward compatibility**: Existing `image_filter` config option must continue to work -- **No tests exist**: `yazi-adapter` has zero tests — any changes are validated at runtime only - -## Key Decisions - -| Decision | Rationale | Outcome | -|----------|-----------|---------| -| Focus on resize algorithm only | User reports sharpness, not color; current pipeline is otherwise functional | — Pending | -| Target iTerm2/WezTerm (IIP) | User's primary terminal; IIP driver calls `Image::downscale()` directly | — Pending | - -## Evolution - -This document evolves at phase transitions and milestone boundaries. - -**After each phase transition** (via `/gsd:transition`): -1. Requirements invalidated? → Move to Out of Scope with reason -2. Requirements validated? → Move to Validated with phase reference -3. New requirements emerged? → Add to Active -4. Decisions to log? → Add to Key Decisions -5. "What This Is" still accurate? → Update if drifted - -**After each milestone** (via `/gsd:complete-milestone`): -1. Full review of all sections -2. Core Value check — still the right priority? -3. Audit Out of Scope — reasons still valid? -4. Update Context with current state - ---- -*Last updated: 2026-03-21 after initialization* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md deleted file mode 100644 index f3e0bf27..00000000 --- a/.planning/ROADMAP.md +++ /dev/null @@ -1,62 +0,0 @@ -# Roadmap: Yazi Image Preview Quality Improvement - -## Overview - -Two phases deliver a surgical, single-file change to `yazi-adapter/src/image.rs`. Phase 1 -implements linear-light-correct downscaling using `fast_image_resize`, covering all resize and -compatibility requirements in one coherent pass (the three correctness invariants — gamma -linearization, alpha premultiplication, filter config mapping — cannot be split without creating -partial-correctness windows). Phase 2 adds the unit test for linear-light correctness and -validates the implementation against the full image-type smoke test matrix. - -## Phases - -**Phase Numbering:** -- Integer phases (1, 2, 3): Planned milestone work -- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) - -Decimal phases appear between their surrounding integers in numeric order. - -- [ ] **Phase 1: Implementation** - Replace both resize call sites with linear-light-correct `fast_image_resize`, add dependency, map all config filter strings -- [ ] **Phase 2: Verification** - Add unit test for linear-light correctness and smoke-test across all image types and terminal protocols - -## Phase Details - -### Phase 1: Implementation -**Goal**: Image previews use linear-light-correct downscaling via `fast_image_resize` with all existing config options preserved -**Depends on**: Nothing (first phase) -**Requirements**: RESIZE-01, RESIZE-02, RESIZE-03, RESIZE-04, RESIZE-05, COMPAT-01, COMPAT-02, COMPAT-03, COMPAT-04 -**Success Criteria** (what must be TRUE): - 1. Opening an image in yazi produces a visibly sharper preview than before (no blurring of fine detail at default lanczos3 filter) - 2. All existing `image_filter` config values (`nearest`, `triangle`, `catmull-rom`, `gaussian`, `lanczos3`) continue to work without error or silent degradation - 3. All terminal protocol drivers (KGP, KGP-old, IIP, Sixel, Chafa, Ueberzug) display images without regressions - 4. `precache()` public signature is unchanged — Lua plugins that call it continue to function - 5. `cargo build --release` succeeds and `cargo clippy --all` produces no new warnings -**Plans:** 1 plan - -Plans: -- [x] 01-01-PLAN.md — Replace resize pipeline with linear-light fast_image_resize (dependency + code + verification) - -### Phase 2: Verification -**Goal**: The implementation is confirmed correct across all image types, protocols, and edge cases, with at least one automated correctness test -**Depends on**: Phase 1 -**Requirements**: TEST-01 -**Success Criteria** (what must be TRUE): - 1. A unit test exists that downscales a 50% gray sRGB patch to 1x1 and asserts the result is within tolerance of the linear-light-correct value (not the sRGB-space-incorrect midpoint) - 2. RGBA PNG with transparency displays without color fringing on transparent edges in IIP/WezTerm - 3. EXIF-rotated JPEG (phone photo) previews with correct orientation and no quality regression - 4. `cargo test --workspace` passes with the new test included -**Plans:** 1 plan - -Plans: -- [ ] 02-01-PLAN.md — Add linear-light correctness unit test and visual verification checklist - -## Progress - -**Execution Order:** -Phases execute in numeric order: 1 -> 2 - -| Phase | Plans Complete | Status | Completed | -|-------|----------------|--------|-----------| -| 1. Implementation | 0/1 | Planned | - | -| 2. Verification | 0/1 | Planned | - | diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md deleted file mode 100644 index eb09b7ad..00000000 --- a/.planning/codebase/ARCHITECTURE.md +++ /dev/null @@ -1,223 +0,0 @@ -# Architecture - -**Analysis Date:** 2026-03-21 - -## Pattern Overview - -**Overall:** Actor-based event-driven TUI application with layered state management - -**Key Characteristics:** -- Async event loop (Tokio) consuming terminal input events and dispatching to named `Actor` implementations -- Strict separation between state (`yazi-core`), actions (`yazi-actor`), and rendering (`yazi-fm` UI widgets) -- Lua scripting layer (`yazi-plugin`) hooks into the same state via `Lives` scoped userdata -- Cross-process pub/sub via a Unix socket server (`yazi-dds`) allowing multiple yazi instances and the `ya` CLI to communicate - -## Layers - -**Entry Point / Binary:** -- Purpose: Initialize all subsystems, start the Tokio event loop, drive `App::serve()` -- Location: `yazi-fm/src/main.rs` -- Contains: `main()`, subsystem `init()` calls in order, global allocator (jemalloc) -- Depends on: Every other crate -- Used by: OS process runner - -**App Loop (`App`):** -- Purpose: Receive `Event` variants from a Tokio MPSC channel; throttle renders (max 10ms interval); coordinate `Dispatcher` -- Location: `yazi-fm/src/app/app.rs` -- Contains: `Core`, `Term`, `Signals`; the `serve()` async loop -- Depends on: `yazi-core`, `yazi-term`, `yazi-actor`, `yazi-shared` -- Used by: `main()` - -**Dispatcher:** -- Purpose: Match each `Event` variant to the right handler — key events go to `Router`, action calls go to `Executor`, render/resize/mouse/paste handled directly -- Location: `yazi-fm/src/dispatcher.rs` -- Contains: `dispatch()` method with exhaustive `Event` match -- Depends on: `Router`, `Executor`, `yazi-actor::Ctx` -- Used by: `App::serve()` - -**Router:** -- Purpose: Translate a `Key` press into an `ActionCow` sequence by looking up `KEYMAP` for the current `Layer` -- Location: `yazi-fm/src/router.rs` -- Contains: `route()`, `matches()`; multi-key chords activate the `which` overlay -- Depends on: `yazi-config::KEYMAP`, `yazi-actor::Ctx` -- Used by: `Dispatcher::dispatch_key()` - -**Executor:** -- Purpose: Dispatch a named `ActionCow` to the correct `Actor::act()` call, organized by `Layer` -- Location: `yazi-fm/src/executor.rs` -- Contains: `execute()` with per-layer handler methods (`app`, `mgr`, `tasks`, `spot`, `pick`, `input`, `confirm`, `help`, `cmp`, `which`, `notify`) -- Depends on: `yazi-actor`, `yazi-macro::act!` -- Used by: `Dispatcher::dispatch_call()` - -**Core State (`Core`):** -- Purpose: Owns all live application state as a single flat struct -- Location: `yazi-core/src/core.rs` -- Contains: `Mgr`, `Tasks`, `Pick`, `Input`, `Confirm`, `Help`, `Cmp`, `Which`, `Notify` -- Depends on: `yazi-core` sub-modules -- Used by: `App`, `Ctx`, `Root` renderer - -**Mgr / Tab:** -- Purpose: File manager state — multiple tabs, yanked clipboard, filesystem watcher, MIME cache -- Location: `yazi-core/src/mgr/mgr.rs`, `yazi-core/src/tab/tab.rs` -- Contains: `Mgr { tabs, yanked, watcher, mimetype }`, `Tab { current, parent, history, selected, spot, preview, finder, search, ... }` -- Depends on: `yazi-fs`, `yazi-watcher`, `yazi-vfs` -- Used by: `Core`, `Ctx` - -**Actor Trait & Implementations:** -- Purpose: Each user-visible command is a zero-size struct implementing `Actor { type Options; fn act(cx, opt) -> Result }` -- Location: `yazi-actor/src/actor.rs` (trait), `yazi-actor/src/mgr/`, `yazi-actor/src/app/`, etc. -- Contains: ~60 mgr actors (cd, arrow, open, yank, paste, search, tab_create, upload...), app actors (bootstrap, resize, plugin, quit...), plus per-overlay actors -- Depends on: `yazi-actor::Ctx`, `yazi-proxy`, `yazi-parser`, `yazi-macro::act!` -- Used by: `Executor` via the `act!` macro - -**Ctx (Action Context):** -- Purpose: Borrows `Core` mutably for the duration of an action; provides convenience accessors for the active tab, current folder, hovered file -- Location: `yazi-actor/src/context.rs` -- Contains: `Ctx { core, term, tab, level, source }`; `Deref`/`DerefMut` into `Core` -- Depends on: `yazi-core::Core`, `yazi-term::Term` -- Used by: every `Actor::act()` implementation - -**Proxy Layer:** -- Purpose: Allow background async tasks (scheduler workers, plugin coroutines) to emit actions without holding `Core` references — they emit `Event::Call` via the shared channel -- Location: `yazi-proxy/src/` -- Contains: `MgrProxy`, `InputProxy`, `CmpProxy`, `TasksProxy`, etc. — thin wrappers that call `emit!(Call(relay!(...)))` -- Depends on: `yazi-shared::event::Event`, `yazi-macro` -- Used by: `yazi-actor` async tasks, `yazi-scheduler`, `yazi-plugin` - -**Parser Layer:** -- Purpose: Typed `Options` structs for every action; decouples serialization/deserialization from actor logic -- Location: `yazi-parser/src/` -- Contains: Per-overlay modules (`mgr`, `app`, `input`, ...) with structs like `CdOpt`, `OpenOpt`, `SortOpt` -- Depends on: `yazi-shared` -- Used by: `yazi-actor` implementations - -**Renderer (`Root`):** -- Purpose: Compose the ratatui `Widget` tree; delegate to Lua for the base UI layout; render overlays in z-order -- Location: `yazi-fm/src/root.rs` -- Contains: `Root::render()` calls Lua `Root:new(area):redraw()`, then renders `Preview`, `Modal`, and all visible overlays -- Depends on: `yazi-binding`, `yazi-plugin::LUA`, `yazi-core::Core` -- Used by: `App::render()` - -**Lua Plugin System:** -- Purpose: Provide a Lua 5.5 scripting environment; expose Rust state as read-only `UserData` via `Lives` scoped bindings -- Location: `yazi-plugin/src/`, `yazi-binding/src/`, `yazi-actor/src/lives/` -- Contains: `Lives::scope()` pins `Core` as a Lua global `cx`; `yazi-binding` exposes element types (Rect, Style, etc.) and state accessors -- Depends on: `mlua`, `yazi-core`, `yazi-config` -- Used by: `Root::render()`, `App::render()`, `AcceptPayload` actor - -**Scheduler:** -- Purpose: Execute file operations, preload/fetch tasks, plugin workers, and external processes with configurable concurrency and priority queues -- Location: `yazi-scheduler/src/` -- Contains: `fetch/`, `file/`, `plugin/`, `preload/`, `process/`, `size/` task types; a `Scheduler` runner -- Depends on: `yazi-proxy`, `yazi-fs`, `yazi-plugin` -- Used by: `yazi-core::Tasks` - -**DDS (Data Distribution Service):** -- Purpose: IPC broker — Unix domain socket server that routes typed `Payload` messages between yazi instances and the `ya` CLI -- Location: `yazi-dds/src/` -- Contains: `Server` (accepts connections, routes by receiver ID and `ability`), `Client` (connects, sends/receives), `Pubsub` (local/remote event subscriptions), `Payload`/`Ember` types -- Depends on: `tokio`, `yazi-shared`, `yazi-boot` -- Used by: `main()` (calls `yazi_dds::serve()`), `yazi-cli` for `emit`/`pub`/`sub` commands - -**VFS / SFTP:** -- Purpose: Abstract filesystem access; local filesystem ops go through `yazi-fs`; remote (SFTP) access goes through `yazi-sftp` + `yazi-vfs::provider` -- Location: `yazi-vfs/src/`, `yazi-sftp/src/` -- Contains: `VfsFile`, `provider::` with local and SFTP providers; `yazi-sftp` implements the SSH/SFTP protocol via `russh` -- Depends on: `russh`, `yazi-fs`, `tokio` -- Used by: `yazi-actor` cd/download/upload actors, `yazi-core` - -**Image Adapter:** -- Purpose: Detect terminal emulator capabilities and render inline images using the best available protocol -- Location: `yazi-adapter/src/` -- Contains: `Adapter` enum (Kgp, KgpOld, Iip, Sixel, Chafa, Ueberzug); `drivers/` with one file per protocol; `yazi-emulator` handles brand/capability detection -- Depends on: `yazi-emulator`, `ratatui` -- Used by: preview rendering in `yazi-fm` - -## Data Flow - -**Key press to screen update:** - -1. `crossterm` event arrives on `Signals` task → `Event::Key` emitted to shared MPSC channel -2. `App::serve()` drains up to 50 events per batch -3. `Dispatcher::dispatch_key()` → `Router::route()` looks up keymap for current `Layer` → emits `Event::Seq` or `Event::Call` -4. `Dispatcher::dispatch_call()` → `Executor::execute()` matches layer + action name → calls `act!(layer:name, cx, action)` -5. `Actor::act()` mutates `Core` fields, optionally spawns async tasks via `tokio::spawn` with result posted back through `Proxy::*` → `emit!(Call(...))` -6. After each event, `NEED_RENDER` atomic is checked; if set and ≥10ms since last frame, `App::render()` is called -7. `Root::render()` calls Lua `cx` (populated via `Lives::scope`), then renders Rust overlay widgets into ratatui `Buffer` -8. `Term::draw()` diffs buffer and writes escape sequences via `crossterm` + `TTY` - -**DDS message from `ya emit`:** - -1. `ya` CLI connects to Unix socket, sends `hi` then payload then `bye` -2. `yazi-dds::Server` routes to connected yazi instance(s) by receiver ID and ability set -3. Receiving yazi instance's `Client` loop receives line, parses `Payload`, calls `payload.emit()` → `Event::Call(accept_payload)` -4. `AcceptPayload::act()` looks up registered Lua handlers in `LOCAL`/`REMOTE` pubsub tables, calls each via `LUA` - -**State management:** - -- `Core` is owned by `App` and mutated synchronously on the Tokio local set thread -- Background tasks (async Tokio tasks) never hold `&mut Core`; they post results back via `Proxy` functions that emit `Event::Call` -- Lua scripts access a read-only snapshot of state through `Lives`-scoped `UserData` bound to the current `Core` reference - -## Key Abstractions - -**`Actor` trait:** -- Purpose: Uniform interface for every command in the system -- Examples: `yazi-actor/src/mgr/cd.rs`, `yazi-actor/src/mgr/open.rs`, `yazi-actor/src/app/bootstrap.rs` -- Pattern: Zero-size structs; `type Options` parsed from `ActionCow`; `fn act(cx, opt) -> Result`; optional `fn hook()` for post-action DDS sparks - -**`Event` enum:** -- Purpose: Central message bus shared between terminal input, action dispatch, and render scheduling -- Location: `yazi-shared/src/event/event.rs` -- Variants: `Call(ActionCow)`, `Seq(Vec)`, `Render(bool)`, `Key`, `Mouse`, `Resize`, `Focus`, `Paste` - -**`Layer` enum:** -- Purpose: Tracks which UI overlay is topmost, determining keymap lookup and executor routing -- Location: `yazi-shared/src/layer.rs` -- Values: `Mgr`, `Tasks`, `Spot`, `Pick`, `Input`, `Confirm`, `Help`, `Cmp`, `Which`, `App`, `Notify` - -**`Ctx` struct:** -- Purpose: Short-lived mutable borrow of `Core` enriched with active tab index and action source; passed to every `Actor::act()` -- Location: `yazi-actor/src/context.rs` -- Pattern: Created by `Ctx::new(&action, core, term)` at start of `Executor` method; `Deref` into `Core` for ergonomic access - -**`RoCell` / `SyncCell`:** -- Purpose: Thread-safe write-once containers used as module-level globals (replacing `lazy_static` / `OnceLock`) -- Location: `yazi-shared/src/ro_cell.rs`, `yazi-shared/src/sync_cell.rs` -- Pattern: `pub static FOO: RoCell = RoCell::new(); ... FOO.init(value);` - -## Entry Points - -**`yazi-fm` binary:** -- Location: `yazi-fm/src/main.rs` -- Triggers: User runs `yazi` in terminal -- Responsibilities: Calls `init()` on every crate in dependency order; starts DDS `serve()`; runs `App::serve()` on Tokio local set - -**`yazi-cli` binary (`ya`):** -- Location: `yazi-cli/src/main.rs` -- Triggers: User runs `ya emit|pub|sub|pkg` -- Responsibilities: Minimal init (shared + fs only); connects to running yazi DDS server for IPC commands; manages plugin packages - -## Error Handling - -**Strategy:** `anyhow::Result` propagated through the actor chain; dispatcher logs errors at `warn` level and continues the loop — failures do not crash the app - -**Patterns:** -- `act!` macro returns `Result`; callers can use `?` to propagate or `.ok()` to silently ignore -- `succ!()` macro returns `Ok(Data::default())` — used to terminate actors that have nothing to return -- `err!(expr)` macro logs expression result as a tracing `error` but does not propagate -- `render!()` macro emits a `Render` event from within `succ!()` when state changed - -## Cross-Cutting Concerns - -**Logging:** `tracing` crate; level controlled via `YAZI_LOG` env var; initialized in `yazi-fm/src/logs.rs` - -**Validation:** Config validation happens at `yazi-config::init()` — parse errors show a blocking prompt then fall back to preset defaults - -**Authentication:** N/A for local mode; SFTP uses `russh` with key-based or password auth negotiated in `yazi-sftp/src/session.rs` - -**Concurrency model:** Single Tokio `LocalSet` thread owns `Core` and runs the event loop; all background I/O is spawned as regular `tokio::spawn` tasks and communicate back via `Event` channel - ---- - -*Architecture analysis: 2026-03-21* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md deleted file mode 100644 index 8285deb3..00000000 --- a/.planning/codebase/CONCERNS.md +++ /dev/null @@ -1,263 +0,0 @@ -# Codebase Concerns - -**Analysis Date:** 2026-03-21 - ---- - -## Tech Debt - -**SFTP URL rebase not implemented:** -- Issue: `UrlBuf::rebase()` panics at runtime for SFTP URLs via `todo!()` macro — hardest-blocking panic -- Files: `yazi-shared/src/url/buf.rs:166` -- Impact: Any code path that calls `rebase()` on an SFTP URL will unconditionally panic at runtime -- Fix approach: Implement the commented-out `Self::Sftp { loc: loc.rebase(base), domain: domain.clone() }` arm - -**`RwFile::metadata()` uses placeholder string for path:** -- Issue: Both Tokio and SFTP arms pass the literal `"// FIXME"` string as the file path -- Files: `yazi-vfs/src/provider/rw_file.rs:23-24` -- Impact: Any metadata query on an open file returns incorrect path information; affects file attributes display -- Fix approach: Capture and store the path when the file is opened; pass it through to `Cha::new()` - -**Splatter legacy `%*` shorthand pending removal:** -- Issue: Three `// TODO: remove` comments mark the `%*` variant (maps to `visit_selected`) as deprecated; dead code kept for backward compat -- Files: `yazi-fs/src/splatter.rs:85,158,247,346` -- Impact: Bloated match arms, confusing maintenance; breaking change deferred -- Fix approach: Remove `%*` handling after a deprecation grace period; update user-facing docs - -**`open_shell_compat` compatibility shim:** -- Issue: Entire `OpenShellCompat` actor, `TasksProxy::open_shell_compat()`, and `match_and_open()` helper all carry `// TODO: remove` comments -- Files: `yazi-actor/src/tasks/open_shell_compat.rs`, `yazi-proxy/src/tasks.rs:10`, `yazi-actor/src/mgr/open_do.rs:61` -- Impact: Multiple call sites continue routing through legacy path instead of new `process_open` -- Fix approach: Migrate all callers to use `TasksProxy::process_exec()` directly; delete the shim actor - -**`ProcessOpenOpt.spread` field marked for removal:** -- Issue: `spread: bool` field carries `// TODO: remove` annotation in the parser type -- Files: `yazi-parser/src/tasks/process_open.rs:17` -- Impact: Dead struct field increasing complexity of every `ProcessOpenOpt` construction -- Fix approach: Remove field; propagate removal to all construction sites in `mgr/shell.rs`, `mgr/open_do.rs`, `tasks/process_open.rs` - -**`ChaMode::is_exec()` deprecated but still in active use:** -- Issue: Method carries `// TODO: deprecate` comment yet is called from `yazi-binding`, `yazi-config` theme code -- Files: `yazi-fs/src/cha/mode.rs:153`, `yazi-binding/src/cha.rs:64`, `yazi-config/src/theme/is.rs:33`, `yazi-config/src/theme/icon.rs:66` -- Impact: Deprecated API surface remains public and actively maintained -- Fix approach: Determine replacement API, update callers, then remove method - -**`vfs::unique_name()` deprecated but infrastructure retained:** -- Issue: `yazi_vfs::unique_name()` is deprecated in favour of `fs.unique()`; Lua wrapper still exposes `fs.unique_name()` with a deprecation warning at runtime -- Files: `yazi-vfs/src/fns.rs:16`, `yazi-plugin/src/fs/fs.rs:234-241` -- Impact: Duplicate code path; external plugins still using the old API receive runtime warnings -- Fix approach: Track down plugin usages via community; remove API after sufficient migration time - -**`Data::into_any2()` has no satisfactory name:** -- Issue: Method acknowledged as needing a better name in comment `// FIXME: find a better name` -- Files: `yazi-shared/src/data/data.rs:233` -- Impact: Poor discoverability in the `Data` API surface -- Fix approach: Audit all callsites and rename; update callers - -**Multiple FIXME-annotated compat `impl AsRef` / `impl Default` on URL types:** -- Issue: `LocBuf`, `UrlCow`, `UrlBuf`, `LocBuf` all carry `// FIXME: remove` impls that exist only for transitional compat -- Files: `yazi-shared/src/loc/buf.rs:23`, `yazi-shared/src/url/cow.rs:21`, `yazi-shared/src/url/buf.rs:16`, `yazi-shared/src/loc/loc.rs:39` -- Impact: These impls can mask type errors; they block further API hardening -- Fix approach: Identify callers relying on these impls; replace with explicit conversions; remove impls - -**Lua `ya.select()` stub not implemented:** -- Issue: `ya.select()` is registered but returns `Ok(())` unconditionally; body is a TODO placeholder -- Files: `yazi-plugin/src/utils/sync.rs:126-128` -- Impact: Any plugin that attempts to call `ya.select()` silently succeeds with no result; misleading API -- Fix approach: Implement select semantics or return an explicit "not implemented" Lua error until complete - -**`spot:copy "line"` unimplemented:** -- Issue: The `"line"` copy type in the spot view has an empty TODO body; no text is copied -- Files: `yazi-actor/src/spot/copy.rs:28` -- Impact: Silent no-op when user copies a line from spot view -- Fix approach: Implement line extraction from the table widget - -**`ya.layer()` cursor and list fields unset:** -- Issue: `cursor: None` and `list: Default::default()` in `ya.input()` / `ya.confirm()` carry `// TODO` comments indicating incomplete options plumbing -- Files: `yazi-plugin/src/utils/layer.rs:57,89` -- Impact: Plugins cannot programmatically position the cursor or pre-populate confirm lists via Lua -- Fix approach: Thread these fields through `InputCfg` / `ConfirmCfg` from the Lua table argument - -**`WATCHER` semaphore acquire unwrap in file watcher:** -- Issue: `WATCHER.acquire().await.unwrap()` can panic if the semaphore is closed during shutdown -- Files: `yazi-watcher/src/local/local.rs:96` -- Impact: Panics possible during clean shutdown sequence -- Fix approach: Use `?` propagation or check for `SemaphoreAcquireError` explicitly - ---- - -## Known Bugs - -**DDS `pub_inner_hi()` error silently swallowed:** -- Symptoms: Peer hello messages fail without any error propagated or logged -- Files: `yazi-dds/src/pubsub.rs:124-125` -- Trigger: IPC transport error during initial hi broadcast -- Workaround: None; failure is invisible - -**Watcher notification batching workaround:** -- Symptoms: File change events are coalesced over 250 ms windows with batch size 1000 as an acknowledged workaround -- Files: `yazi-watcher/src/local/local.rs:89` -- Trigger: Comment says `// TODO: revert this once a new notification is implemented`; current impl is not the intended design -- Workaround: Current behaviour is functional but has fixed latency overhead - -**LINKED returns Path type instead of Url:** -- Symptoms: Symlink-based watch targets use an incorrect type, requiring an extra `Url::regular()` wrapping -- Files: `yazi-watcher/src/reporter.rs:33-35` -- Trigger: Any access to watched symlinked directories -- Workaround: Current adapter in place but acknowledged as wrong - -**`UrlCov::parent()` not used in selection remove path:** -- Symptoms: Parent traversal in `selected.rs` uses a manual workaround instead of the correct `UrlCov::parent()` API -- Files: `yazi-core/src/tab/selected.rs:111` -- Trigger: Removing selected files that have deep parent chains -- Workaround: Manual `while let Some(u) = parent` loop - ---- - -## Security Considerations - -**`unsafe std::env::set_var()` in async context:** -- Risk: `set_var` is unsound in multi-threaded programs; marked `unsafe` in Rust 1.83+. Called from a `spawn_blocking` closure that runs concurrently with other threads reading the environment -- Files: `yazi-fs/src/cwd.rs:101,108`, `yazi-dds/src/lib.rs:25-33` -- Current mitigation: `unsafe` block is present acknowledging the risk; init happens early in `dds::init()` -- Recommendations: Migrate environment variables to explicit `Arc` passing or use `std::env::set_var` only before any threads are spawned - -**`str::from_utf8_unchecked()` without SAFETY comments:** -- Risk: If upstream data is not validated as valid UTF-8 before calling the unchecked variant, memory safety of `str` references is violated -- Files: `yazi-adapter/src/drivers/kgp.rs:360,366`, `yazi-adapter/src/drivers/kgp_old.rs:49,55`, `yazi-plugin/src/external/highlighter.rs:188`, `yazi-shared/src/strand/buf.rs:68,85,90` -- Current mitigation: No `// SAFETY` comment explaining the invariant; workspace lint `missing_safety_doc = "allow"` suppresses the Clippy warning globally (`Cargo.toml:87`) -- Recommendations: Add `// SAFETY:` comments to each site explaining why the bytes are valid UTF-8; re-enable `missing_safety_doc` lint - -**`missing_safety_doc` lint globally suppressed:** -- Risk: All `unsafe fn` blocks in the workspace are exempt from documentation requirements -- Files: `Cargo.toml:87` -- Current mitigation: None -- Recommendations: Remove `missing_safety_doc = "allow"` from workspace lints; fix each site - ---- - -## Performance Bottlenecks - -**`Refresh::trigger_dirs()` acknowledged slow path:** -- Problem: Spawns async tasks to stat and reload every visible folder on every refresh event; acknowledged in comment `// TODO: performance improvement` -- Files: `yazi-actor/src/mgr/refresh.rs:44` -- Cause: No caching layer; unconditional `Files::assert_stale()` and `Files::from_dir_bulk()` calls per folder -- Improvement path: Track change timestamps and skip re-reads when stat hasn't changed; debounce refresh triggers - -**`icon()` method computes icon match on every call without caching:** -- Problem: `yazi-actor/src/lives/file.rs:91` and `yazi-binding/src/file.rs:93` both call `THEME.icon.matches()` per file on every render with `// TODO: use a cache` -- Files: `yazi-actor/src/lives/file.rs:91`, `yazi-binding/src/file.rs:93` -- Cause: No memoization on `File` for the computed icon -- Improvement path: Cache the icon result on the `File` struct after first computation; invalidate on theme reload - -**`Pos::to_rect()` caching absent:** -- Problem: Rect computation from `Pos` has `// TODO: cache` comment indicating repeated recomputation -- Files: `yazi-binding/src/elements/pos.rs:84` -- Cause: No cached field -- Improvement path: Store computed `Rect` alongside `Pos`; invalidate when terminal size changes - -**245 `.clone()` calls across the codebase:** -- Problem: URL types (`UrlBuf`, `UrlCow`) are cloned pervasively; 126 `unwrap()`/`expect()` calls in non-test production code -- Files: spread across all crates -- Cause: URL types carry owned strings with no interning beyond the `Pool` for symbols; clone-heavy IPC message passing -- Improvement path: Audit hot paths using `clone()` on URLs; leverage existing `UrlCow` borrow types where ownership is not needed - ---- - -## Fragile Areas - -**`yazi-fs/src/splatter.rs` — shell argument expansion:** -- Files: `yazi-fs/src/splatter.rs` -- Why fragile: Multiple `unreachable!()` arms in `visit_hovered()`, `visit_dirname()` etc. that panic if the character dispatch logic in `visit()` is ever inconsistent; legacy `%*` arms awaiting removal add more match paths -- Safe modification: Run full `cargo test -p yazi-fs` after any change; ensure all `visit_*` dispatch matches the `visit()` character table exactly -- Test coverage: Inline `#[test]` block exists for basic cases; no exhaustive round-trip tests for all interpolation forms - -**`yazi-shared/src/url/` — URL type hierarchy:** -- Files: `yazi-shared/src/url/url.rs`, `yazi-shared/src/url/buf.rs`, `yazi-shared/src/url/cow.rs` -- Why fragile: Parallel enum hierarchies (`Url`, `UrlBuf`, `UrlCow`) must stay in sync; SFTP arm incomplete in `rebase()`; several compat `impl` blocks marked for removal; any variant addition requires changes in all three types plus `Loc`/`LocBuf` -- Safe modification: Add variants to all three enums simultaneously; verify no `_ =>` arms silently absorb new variants; check all `From`/`Into` impls -- Test coverage: Unit tests exist in `yazi-shared/src/url/buf.rs` and `cow.rs` but do not cover SFTP variant paths - -**`yazi-dds` — inter-process pub/sub:** -- Files: `yazi-dds/src/pubsub.rs`, `yazi-dds/src/lib.rs` -- Why fragile: Uses `unsafe { std::env::set_var }` during init; `pub_inner_hi()` silently ignores errors; global statics (`ID`, `PEERS`, `LOCAL`, `REMOTE`) initialized via `RoCell::init()` which panics on double-init -- Safe modification: Only call `dds::init()` once before spawning any async tasks; never call from tests without isolation -- Test coverage: No tests found in `yazi-dds/` - -**`yazi-adapter` — image protocol drivers:** -- Files: `yazi-adapter/src/drivers/kgp.rs`, `yazi-adapter/src/drivers/kgp_old.rs` -- Why fragile: Two parallel KGP driver implementations exist (current and `_old`); both use `str::from_utf8_unchecked()` without SAFETY docs; base64-encoded image data passed through format strings -- Safe modification: Treat `kgp_old.rs` as read-only reference; do not add features there; changes to base64 chunking must preserve chunk boundary alignment -- Test coverage: No unit tests found; runtime-only validation - -**`yazi-plugin/src/isolate/` — Lua plugin isolation:** -- Files: `yazi-plugin/src/isolate/peek.rs`, `yazi-plugin/src/isolate/preload.rs`, `yazi-plugin/src/isolate/fetch.rs`, `yazi-plugin/src/isolate/spot.rs` -- Why fragile: Uses `Handle::current().block_on()` which blocks the calling OS thread; if called from within an async context on the Tokio thread pool this starves the executor -- Safe modification: Only invoke isolate entry points from dedicated `spawn_blocking` threads, never from `async` tasks directly -- Test coverage: No unit tests; integration-tested only via running yazi - ---- - -## Scaling Limits - -**Lua `ya.select()` stub:** -- Current capacity: Not functional; always returns empty result -- Limit: Any plugin relying on multi-item selection UI is broken -- Scaling path: Implement in `yazi-plugin/src/utils/sync.rs` - -**File watcher batch size hardcoded at 1000:** -- Current capacity: 1000 change events per 250 ms window -- Limit: Repositories or directories with high churn (e.g., build output) may overwhelm the batch and drop events -- Scaling path: Make `chunks_timeout` size/duration configurable; implement proper OS-level coalescing - ---- - -## Dependencies at Risk - -**`ratatui` unstable features:** -- Risk: Uses `unstable-rendered-line-info` and `unstable-widget-ref` feature flags; these APIs can break without a semver guarantee between minor ratatui versions -- Impact: Build breaks or behavioural regressions on ratatui upgrades -- Migration plan: Track ratatui changelog; stabilisation of these features is planned upstream but no ETA - -**`mlua` with Lua 5.5:** -- Risk: `lua55` feature targets Lua 5.5 which is not yet a stable release; API changes between Lua 5.5 preview builds could require code updates -- Impact: `yazi-plugin` and `yazi-binding` crates rely on Lua 5.5 semantics -- Migration plan: Pin to a tested mlua version; test against new Lua 5.5 release candidates in CI - ---- - -## Test Coverage Gaps - -**`yazi-dds` — zero tests:** -- What's not tested: IPC message serialization, pub/sub routing, peer discovery, all `pubsub.rs` paths -- Files: `yazi-dds/src/pubsub.rs`, `yazi-dds/src/client.rs`, `yazi-dds/src/server.rs` -- Risk: Silent regressions in cross-instance communication; `pub_inner_hi()` error swallowing undetected -- Priority: High - -**`yazi-adapter` — zero tests:** -- What's not tested: All image protocol drivers (KGP, KGP-old, Sixel, IIP, Ueberzug), ICC colour transform, image encoding -- Files: `yazi-adapter/src/drivers/`, `yazi-adapter/src/icc.rs` -- Risk: Image rendering regressions not caught until runtime -- Priority: High - -**`yazi-vfs` SFTP provider paths — sparse tests:** -- What's not tested: `RwFile` metadata with SFTP, `try_absolute()` for archive URLs, SFTP-specific `Cha` conversion -- Files: `yazi-vfs/src/provider/rw_file.rs`, `yazi-vfs/src/provider/provider.rs` -- Risk: Archive and SFTP URL handling silently broken; placeholder `"// FIXME"` path in production unnoticed -- Priority: High - -**`yazi-actor` core actions — partial tests:** -- What's not tested: `bulk_rename` cycle detection edge cases, `open_do::match_and_open()` legacy path, `spot::copy "line"` variant -- Files: `yazi-actor/src/mgr/bulk_rename.rs`, `yazi-actor/src/mgr/open_do.rs`, `yazi-actor/src/spot/copy.rs` -- Risk: Silent regressions in file operations; no-op copy undetected -- Priority: Medium - -**Only 23 of 912 Rust source files contain tests (~2.5%):** -- What's not tested: The vast majority of crate logic including scheduler, config parsing, most of the actor layer, watcher, and binding -- Files: All crates except `yazi-shared`, `yazi-fs`, `yazi-core` (partial), `yazi-config` (partial) -- Risk: Wide surface area for undetected regressions; TDD mandate in CLAUDE.md is not yet reflected in coverage -- Priority: High - ---- - -*Concerns audit: 2026-03-21* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md deleted file mode 100644 index 4e5d5de2..00000000 --- a/.planning/codebase/CONVENTIONS.md +++ /dev/null @@ -1,175 +0,0 @@ -# Coding Conventions - -**Analysis Date:** 2026-03-21 - -## Naming Patterns - -**Files:** -- `snake_case.rs` for all Rust source files -- Module directories named after the concept they contain (e.g., `yazi-shared/src/url/`, `yazi-fs/src/path/`) -- Error types live in dedicated `error.rs` or `error/` files per crate -- Config presets in `preset/` subdirectories - -**Structs and Enums:** -- `PascalCase` for all types: `UrlBuf`, `LocBuf`, `Backstack`, `Selected`, `SchemeCow` -- `Buf`-suffixed types are owned buffers; reference types use the bare name (e.g., `Url` vs `UrlBuf`, `Loc` vs `LocBuf`) -- `Cow`-suffixed types are clone-on-write wrappers (e.g., `UrlCow`, `SchemeCow`, `PathCow`) -- Error enums use `Error` suffix with domain prefix: `PathDynError`, `StripPrefixError`, `SetNameError` - -**Functions:** -- `snake_case` for all functions and methods -- Boolean getters often use `is_` prefix: `is_empty()`, `is_dir()`, `is_local()`, `is_regular()` -- Public accessor stubs that return a borrowed view often match the field name: `uri()`, `urn()`, `base()`, `trail()` -- Fallible constructors use `try_` prefix: `try_join()`, `try_set_name()`, `try_strip_prefix()` -- Conversion constructors named by intent: `zeroed()`, `floated()`, `saturated()`, `from_components()` -- Async variants of sync functions stay named the same (not `_async` suffixed) - -**Variables:** -- `snake_case`, single-letter names only for iterators (`i`, `c`, `b`) and closures -- Avoid abbreviations; prefer `url`, `path`, `domain` over `u`, `p`, `d` in function signatures -- Tuple/temporary intermediate values named `a`, `b` when context is obvious - -**Traits:** -- `PascalCase`; `Able` suffix for marker/capability traits: `LocAble`, `LocBufAble`, `Splatable` -- `Like` suffix for view-oriented traits: `UrlLike`, `PathLike`, `SchemeLike`, `StrandLike` -- `As` prefix for conversion traits: `AsUrl`, `AsPath`, `AsScheme`, `AsStrand` - -**Constants and Statics:** -- `UPPER_SNAKE_CASE` for statics: `ADAPTOR`, `WSL`, `WATCHED`, `WATCHER` -- Associated constants inside `impl` use `UPPER_SNAKE_CASE`: `const NAME: &str = "bulk_rename"` - -## Code Style - -**Formatting:** -- Tool: `rustfmt` (nightly toolchain required) -- Config: `rustfmt.toml` at repo root -- Hard tabs for indentation (not spaces) -- Tab width: 2 spaces equivalent -- Edition 2024 style -- Imports grouped: std → external crates → workspace crates (via `group_imports = "StdExternalCrate"`) -- Imports within a group horizontally collapsed: `use std::{borrow::Cow, ffi::OsStr, ...}` -- One-liner functions on a single line when possible (`fn_single_line = true`) -- Struct field alignment enabled up to 99 chars: fields visually aligned with spaces - -**Linting:** -- Tool: Clippy (stable) -- Config: `[workspace.lints.clippy]` in `Cargo.toml` -- `format_push_string = "warn"` – avoid `push_str(&format!(...))`, use `write!` instead -- `implicit_clone = "warn"` – `.to_owned()` or `.clone()` must be explicit -- `use_self = "warn"` – use `Self` instead of the concrete type name inside `impl` blocks -- `if_same_then_else = "allow"` – duplicate branches permitted -- `module_inception = "allow"` – `mod foo { struct Foo }` pattern is allowed - -**Lua:** -- Tool: StyLua -- Config: `stylua.toml` at repo root -- Lua 5.4 syntax -- Indent width: 2 spaces -- `call_parentheses = "NoSingleTable"` – omit parentheses when the only argument is a table -- `sort_requires = true` – `require()` calls are sorted alphabetically - -## Import Organization - -**Rust order (enforced by rustfmt):** -1. `std` / `core` / `alloc` -2. External crates (e.g., `anyhow`, `mlua`, `tokio`) -3. Workspace crates (e.g., `yazi_shared`, `yazi_fs`) -4. Local / crate-internal: `crate::`, `super::`, `self::` - -**Path Aliases:** -- No `use` aliases used in production code; type aliases via `type` declaration where needed -- In tests, `use super::*` is the universal import pattern - -**Module Export Pattern:** -- `yazi_macro::mod_pub!(a b c)` – re-exports submodules as `pub mod` (subdirectories visible) -- `yazi_macro::mod_flat!(a b c)` – makes private `mod` items `pub use`d flat into current namespace -- Avoids manual `mod x; pub use x::*;` repetition throughout all crates - -## Error Handling - -**Boundary strategy:** -- Functions that can fail return `anyhow::Result` at application layer (actors, async tasks) -- Domain-specific errors use `thiserror::Error`-derived enums with precise variants (see `yazi-shared/src/path/error.rs`, `yazi-shared/src/strand/error.rs`) -- Error propagation uses `?` operator; `.context()` added where clarification helps -- `bail!()` from `anyhow` used for early failure with message -- `ensure!()` from `anyhow` used for condition checks - -**Error macro (`err!`):** -- `yazi_macro::err!(expr)` – logs error via `tracing::error!` and discards result; used for fire-and-forget side effects -- `yazi_macro::err!(expr, "fmt {}", args)` – same but with custom message -- Do NOT use `unwrap()` in production code; `expect("message")` is acceptable only when invariant is guaranteed -- `debug_assert!(...)` used inside `impl` blocks to verify post-conditions cheaply - -**Panics:** -- `unreachable!()` used only inside exhaustive match arms that should logically never be hit -- `expect("...")` used in constructors/init code where failure means programmer error - -## Logging - -**Framework:** `tracing` crate - -**Import pattern:** -```rust -use tracing::{debug, error, warn}; -// or targeted: -use tracing::error; -``` - -**Patterns:** -- `tracing::error!(...)` – non-recoverable errors in async tasks -- `tracing::warn!(...)` – degraded state that can continue -- `tracing::debug!(...)` – diagnostic information (only in debug builds via `max_level_debug`) -- `yazi_macro::err!(expr)` – shorthand to log and discard a `Result::Err` -- `yazi_macro::time!(expr)` – wraps an expression in a debug-level timing log -- Log level controlled at runtime via `YAZI_LOG` environment variable - -## Comments - -**When to Comment:** -- Inline comments explain WHY, not WHAT: `// Only keep 30 URLs before the cursor, the cleanup threshold is 60` -- Code sections within large files use `// ---` separator lines: `// --- Tuple`, `// --- Tests` -- Platform-conditional code annotated: `// "/" is both a directory separator and the root directory per se` -- Pending work uses `// TODO:` or `// FIXME:` markers (not `//TODO:`) - -**Doc comments (`///`):** -- Used only on public API items with non-obvious contracts -- CLI arguments all have `///` docs (powers `--help` output via `clap`) -- Internal implementation functions rarely have doc comments - -**Commented-out code:** -- Not present; dead code is removed immediately - -## Function Design - -**Size:** Typically 5–25 lines; business logic delegated to private helpers - -**Parameters:** Max 3 in most cases; larger argument sets use an Options struct (e.g., `BulkRenameOpt`, `ShowOpt`) - -**Return Values:** -- `Option` for absence; `Result` for failure -- `bool` return for mutation success/failure: `add()`, `remove()` on `Selected` -- Avoid `(bool, T)` tuples; use named return types or structs - -**Boolean Parameters:** -- Avoided in public APIs; use enums or named Options structs instead -- Acceptable in private helpers when the call site is immediately adjacent - -## Module Design - -**Exports:** -- All crates have a `pub fn init()` entry point for global state initialization -- Each crate exposes a flat public API via `mod_flat!` – callers do not need to know internal module layout -- Sub-namespaces exposed via `mod_pub!` when logical grouping is useful (e.g., `elements/`, `path/`) - -**Barrel Files:** -- `lib.rs` serves as the crate root; uses macros instead of manual barrel re-exports -- No separate `mod.rs` barrel files – directory modules are declared in `lib.rs` - -**Struct Internals:** -- Private fields by default; getters expose them where needed -- `pub(super)` used for intra-module access (e.g., `pub(super) inner`, `pub(super) area`) -- Newtype pattern common for domain types: `struct Backstack { cursor: usize, stack: Vec }` - ---- - -*Convention analysis: 2026-03-21* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md deleted file mode 100644 index a1a80d68..00000000 --- a/.planning/codebase/INTEGRATIONS.md +++ /dev/null @@ -1,208 +0,0 @@ -# External Integrations - -**Analysis Date:** 2026-03-21 - -## APIs & External Services - -**None (network-based APIs):** -- Yazi is a local terminal file manager. It does not call any external web APIs or cloud services at runtime. -- All "integrations" are protocol-level or subprocess-based. - -## SSH / SFTP - -**SSH/SFTP Protocol:** -- Implementation: `yazi-sftp/` crate -- SDK/Client: `russh` 0.57.1 (pure-Rust SSH2 implementation, ring + rsa features) -- Purpose: Browse and operate on remote filesystems over SFTP -- Session management: `yazi-sftp/src/session.rs` — async session with per-request oneshot callbacks -- Protocol: Custom SFTP packet serialization/deserialization in `yazi-sftp/src/ser.rs`, `de.rs` -- Auth: SSH key-based (RSA supported via `russh`); no env var required, handled by SSH session negotiation -- VFS integration: Remote paths handled by `typed-path` for cross-platform path normalization - -## Image Display Protocols - -**Terminal Image Rendering (`yazi-adapter/`):** - -Yazi probes the terminal emulator and selects the best supported image protocol: - -| Protocol | Driver file | Terminals | -|---|---|---| -| Kitty Graphics Protocol (KGP) | `yazi-adapter/src/drivers/kgp.rs` | Kitty, Ghostty | -| KGP Old (chunked) | `yazi-adapter/src/drivers/kgp_old.rs` | Konsole, Warp | -| iTerm2 Inline Protocol (IIP) | `yazi-adapter/src/drivers/iip.rs` | iTerm2, WezTerm, VSCode, Tabby, Hyper, Mintty, Rio, Bobcat | -| Sixel | `yazi-adapter/src/drivers/sixel.rs` | Foot, Microsoft Terminal, BlackBox, VSCode, Rio, Bobcat | -| Ueberzug++ | `yazi-adapter/src/drivers/ueberzug.rs` | Fallback via external process | -| Chafa | `yazi-adapter/src/drivers/chafa.rs` | Default fallback (text-mode rendering) | - -Detection logic: `yazi-adapter/src/adapters.rs` — maps `yazi_emulator::Brand` enum to adapter list. - -**ICC Color Management:** -- `moxcms` 0.8.1 — reads embedded ICC profiles from images -- `palette` 0.7.6 — color space conversion -- `quantette` 0.5.1 — color quantization for Sixel/Chafa output - -## Terminal Emulator Detection - -**Emulator Database (`yazi-emulator/`):** -- Detects running terminal via escape sequence queries and environment variables -- Recognized brands: Kitty, Konsole, iTerm2, WezTerm, Foot, Ghostty, Microsoft Terminal, Warp, Rio, BlackBox, VSCode, Tabby, Hyper, Mintty, Tmux, VTerm, Apple Terminal, Urxvt, Bobcat -- Light/dark theme detection feeds into `yazi-config` flavor selection -- Tmux multiplexer passthrough handled via `yazi-adapter/src/lib.rs` -- WSL detection via `yazi_shared::in_wsl()` - -## Data Storage - -**Databases:** -- None (no embedded database) - -**File Storage:** -- Local filesystem only, via Rust standard library and `yazi-fs/` crate -- Trash/recycle bin: `trash` 5.2.5 crate (all non-Android platforms) in `yazi-fs/` -- Preview image cache: configurable directory via `preview.cache_dir` in `yazi-config/preset/yazi-default.toml` - -**Caching:** -- In-process LRU cache: `lru` 0.16.3 in `yazi-scheduler/` -- No external cache service - -## Plugin System - -**Lua Plugin System (`yazi-plugin/`):** -- Runtime: Lua 5.5 embedded via `mlua` 0.11.6 (vendored by default) -- Prebuilt assets: `yazi-prebuilt` 0.1.0 crate bundles compiled Lua plugins -- Built-in plugins: `yazi-plugin/preset/plugins/` — archive, code, dds, image, video, pdf, svg, json, font, fzf, zoxide, mime, session, vfs, etc. -- Plugin API exposed as `ya` global in Lua; see `yazi-plugin/preset/ya.lua` -- Async plugin execution via `ya.co()` coroutine API - -## Data Distribution Service (DDS) - -**Inter-process Communication (`yazi-dds/`):** -- Purpose: Communication between multiple yazi instances (e.g., nesting, shell integration) -- Transport: Unix domain socket (or named pipe on Windows), using Tokio async I/O -- Protocol: JSON-serialized messages (`serde_json`) -- Environment variables set/read: `YAZI_ID`, `YAZI_PID`, `YAZI_LEVEL` -- Build embeds git metadata via `vergen-gitcl` - -## Shell Integration - -**CLI Tool (`yazi-cli/` → binary `ya`):** -- Shell completion generated at build time for: bash, zsh, fish, elvish, nushell, fig -- Libraries: `clap_complete` 4.6.0, `clap_complete_nushell` 4.6.0, `clap_complete_fig` 4.5.2 -- `ya pub` / `ya emit` commands for DDS messaging from shell scripts - -## Filesystem Watching - -**File Watcher (`yazi-watcher/`):** -- Library: `notify` 8.2.0 with `macos_fsevent` feature -- macOS: FSEvents API via feature flag -- Linux: inotify (via notify default backend) -- Windows: ReadDirectoryChanges (via notify default backend) - -## External Programs (Subprocess Integrations) - -Yazi shells out to external programs configured in `yazi-config/preset/yazi-default.toml`. These are not hard dependencies but expected to be present in `$PATH`: - -**File Operations:** -- `$EDITOR` / `vi` — text editing -- `code` — VS Code editor (Windows) -- `xdg-open` — open files (Linux) -- `open` — open files (macOS) -- `termux-open` — open files (Android) - -**Media:** -- `mediainfo` — media file metadata display -- `exiftool` — EXIF data display - -**Archives:** -- Handled internally via `ya pub extract` (DDS command) - -**Fuzzy Finder:** -- `fzf` — optional, via `yazi-plugin/preset/plugins/fzf.lua` - -**Directory Jumping:** -- `zoxide` — optional, via `yazi-plugin/preset/plugins/zoxide.lua` - -**Image Fallback:** -- `ueberzug++` — external process fallback for image display -- `chafa` — text-mode image rendering fallback - -**Video Preview:** -- External video thumbnailing via `yazi-plugin/preset/plugins/video.lua` - -**PDF Preview:** -- External PDF rendering via `yazi-plugin/preset/plugins/pdf.lua` - -**SVG Preview:** -- External SVG rendering via `yazi-plugin/preset/plugins/svg.lua` - -**Font Preview:** -- `yazi-plugin/preset/plugins/font.lua` - -## Authentication & Identity - -**Auth Provider:** -- None (no user accounts or auth service) -- SSH auth for SFTP handled by `russh` (key-based) - -## Monitoring & Observability - -**Error Tracking:** -- None (no external error tracking service) - -**Logging:** -- `tracing` 0.1.44 framework with `tracing-subscriber` 0.3.23 -- Log appender: `tracing-appender` 0.2.4 writes to file -- Max log level: `debug` in both dev and release builds (via `max_level_debug` and `release_max_level_debug` features) -- Configurable via `RUST_LOG` environment variable (env-filter feature) - -**Panic Handling:** -- `better-panic` 0.3.0 in `yazi-fm/` for improved panic display - -## CI/CD & Deployment - -**Hosting:** -- GitHub Releases (binary archives per platform/arch) -- Snap Store (Linux, via `snap/`) -- Winget (Windows Package Manager) -- Nix (via `flake.nix` and nixpkgs) - -**CI Pipeline:** -- GitHub Actions (`.github/workflows/`) - - `test.yml` — build + `cargo test --workspace` on Ubuntu, Windows, macOS - - `check.yml` — clippy (stable), rustfmt (nightly), stylua - - `publish.yml` — on release: publish to Winget and Snap Store - - `cachix.yml` — Nix binary cache - - `draft.yml` — release draft automation - - `lock.yml` — dependency lock file updates -- Build cache: `sccache` via `mozilla-actions/sccache-action` - -**Release Distribution:** -- Binary installs via `cargo-binstall` (metadata in `[package.metadata.binstall]`) -- Archive naming: `yazi-{target}.{archive-suffix}` from GitHub Releases - -## Webhooks & Callbacks - -**Incoming:** -- None - -**Outgoing:** -- None - -## Environment Configuration - -**Required for build:** -- Rust stable toolchain (MSRV 1.92.0) -- `MACOSX_DEPLOYMENT_TARGET=10.12` (set automatically via `.cargo/config.toml`) - -**Required for CI secrets:** -- `WINGET_TOKEN` — publishing to Windows Package Manager -- `SNAPCRAFT_TOKEN` — publishing to Snap Store -- `GITHUB_TOKEN` — stylua action authentication - -**Optional runtime:** -- `EDITOR` — preferred text editor (falls back to `vi`) -- `YAZI_CONFIG_HOME` — override config directory -- `RUST_LOG` — log level filter - ---- - -*Integration audit: 2026-03-21* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md deleted file mode 100644 index ff415f92..00000000 --- a/.planning/codebase/STACK.md +++ /dev/null @@ -1,146 +0,0 @@ -# Technology Stack - -**Analysis Date:** 2026-03-21 - -## Languages - -**Primary:** -- Rust 2024 edition - All core crates (`yazi-fm/`, `yazi-cli/`, `yazi-core/`, all `yazi-*` crates) - -**Secondary:** -- Lua 5.5 (embedded via mlua) - Plugin system (`yazi-plugin/preset/plugins/*.lua`, `yazi-plugin/preset/ya.lua`) -- Nix - Development environment and packaging (`flake.nix`, `nix/`) -- TOML - Configuration format (`yazi-config/preset/*.toml`) - -## Runtime - -**Environment:** -- Native binary, no runtime required -- Platform targets: Linux, macOS, Windows, Android (via Termux) -- WSL (Windows Subsystem for Linux) supported with special detection logic - -**Package Manager:** -- Cargo (Rust) -- Lockfile: `Cargo.lock` (present, committed) - -## Frameworks - -**Core:** -- `ratatui` 0.30.0 - TUI rendering framework (used in `yazi-fm/`, `yazi-widgets/`, `yazi-adapter/`) -- `crossterm` 0.29.0 - Cross-platform terminal I/O, with `use-dev-tty` on macOS -- `tokio` 1.50.0 (full features) - Async runtime powering the entire application - -**Lua Embedding:** -- `mlua` 0.11.6 - Lua 5.5 embedding with `async`, `anyhow`, `serde`, `macros` features -- `vendored-lua` feature flag bundles Lua at compile time (default) - -**Serialization:** -- `serde` 1.0.228 with derive - universal serialization -- `serde_json` 1.0.149 - JSON for DDS protocol -- `toml` 1.0.6 - Configuration file parsing -- `serde_with` 3.18.0 - Advanced serde helpers - -**Build/Dev:** -- `rustfmt` nightly - Code formatting (`rustfmt.toml`) -- `clippy` stable - Linting (rules in root `Cargo.toml` `[workspace.lints.clippy]`) -- `stylua` latest - Lua code formatting (`stylua.toml`, Lua 5.4 syntax target) -- `vergen-gitcl` 9.1.0 - Build-time git metadata embedding (in `yazi-dds`, `yazi-cli` build scripts) -- `sccache` - Build caching in CI - -## Key Dependencies - -**Critical:** -- `mlua` 0.11.6 - Lua plugin system; entire extensibility model depends on this -- `ratatui` 0.30.0 - All UI rendering; uses unstable features `unstable-rendered-line-info`, `unstable-widget-ref` -- `tokio` 1.50.0 - All async I/O; scheduler, watcher, DDS, SFTP all depend on it -- `russh` 0.57.1 - SSH/SFTP client (ring + rsa features) used in `yazi-sftp/` - -**Image Rendering:** -- `image` 0.25.10 - Image decoding (avif, bmp, dds, exr, gif, hdr, ico, jpeg, png, pnm, qoi, tga, tiff, webp) in `yazi-adapter/` -- `moxcms` 0.8.1 - ICC color management in `yazi-adapter/` -- `palette` 0.7.6 - Color manipulation in `yazi-adapter/` -- `quantette` 0.5.1 - Color quantization in `yazi-adapter/` - -**Syntax Highlighting:** -- `syntect` 5.3.0 - Syntax highlighting with `plist-load` and `regex-onig` features in `yazi-plugin/` - -**Filesystem:** -- `notify` 8.2.0 - Filesystem watching with `macos_fsevent` feature in `yazi-watcher/` -- `trash` 5.2.5 - Cross-platform trash/recycle bin (all non-Android targets) in `yazi-fs/` -- `typed-path` 0.12.3 - Cross-platform path handling (for SFTP and VFS) - -**Performance:** -- `tikv-jemallocator` 0.6.1 - jemalloc allocator on non-macOS/non-Windows (configured in `.cargo/config.toml`) -- `parking_lot` 0.12.5 - Fast mutexes and RwLocks throughout -- `foldhash` 0.2.0 - Fast hash function -- `hashbrown` 0.16.1 - Fast hash maps -- `lru` 0.16.3 - LRU cache in `yazi-scheduler/` -- `twox-hash` 2.1.2 - xxHash3_128 for content hashing - -**Utility:** -- `anyhow` 1.0.102 - Error handling throughout -- `thiserror` 2.0.18 - Error type derivation -- `clap` 4.6.0 - CLI argument parsing in `yazi-cli/`, `yazi-boot/` -- `clap_complete`, `clap_complete_nushell`, `clap_complete_fig` - Shell completion generation -- `chrono` 0.4.44 - Date/time -- `regex` 1.12.3 - Regular expressions -- `globset` 0.4.18 - Glob pattern matching for file rules -- `percent-encoding` 2.3.2 - URL encoding for VFS paths -- `base64` 0.22.1 - Encoding for image protocols -- `rand` 0.9.2 - Random number generation -- `tracing` 0.1.44 + `tracing-subscriber` 0.3.23 - Structured logging -- `tracing-appender` 0.2.4 - Log file appending -- `async-priority-channel` 0.2.0 - Priority task queue in `yazi-scheduler/` -- `yazi-prebuilt` 0.1.0 - Prebuilt Lua plugin assets bundled into `yazi-plugin/` - -**Platform-specific:** -- `libc` 0.2.183 - Unix FFI (Linux, macOS) -- `uzers` 0.12.2 - Unix user/group info (Linux, macOS) -- `windows-sys` 0.61.2 - Windows API bindings (Windows; JobObjects, Storage, UI Shell) -- `core-foundation-sys` 0.8.7 - macOS CoreFoundation (macOS only) -- `objc2` 0.6.4 - Objective-C bindings (macOS only, `yazi-fs/`) -- `signal-hook-tokio` 0.4.0 - Unix signal handling in `yazi-fm/` - -## Configuration - -**Environment:** -- No `.env` files; configured through TOML files placed in XDG config dirs -- Runtime env vars used: `YAZI_ID`, `YAZI_LEVEL`, `YAZI_PID`, `EDITOR`, `YAZI_CONFIG_HOME` -- Build-time env via `.cargo/config.toml`: `MACOSX_DEPLOYMENT_TARGET=10.12`, jemalloc settings - -**Build:** -- Root `Cargo.toml` - Workspace manifest with centralized dependency versions -- `.cargo/config.toml` - Allocator config, macOS deployment target, aarch64 CPU flags -- `rustfmt.toml` - Formatter config (nightly, hard tabs, 2-space tab width, Unix newlines) -- `stylua.toml` - Lua formatter config (Lua 5.4 syntax, 2-space indent) - -**Configuration files (user-facing):** -- `yazi-config/preset/yazi-default.toml` - Manager, preview, opener, open rules -- `yazi-config/preset/keymap-default.toml` - Keybindings -- `yazi-config/preset/theme-dark.toml`, `theme-light.toml` - Color themes -- `yazi-config/preset/vfs-default.toml` - Virtual filesystem settings - -## Build Profiles - -**dev:** Line-tables debug info only; dependencies built without debug info -**release:** LTO=fat, codegen-units=1, panic=abort, strip=true -**release-windows:** Inherits release but panic=unwind -**dev-opt:** Release settings but 256 codegen-units, incremental=true, LTO off - -## Platform Requirements - -**Development:** -- Rust stable toolchain (minimum MSRV 1.92.0) -- Rust nightly for `rustfmt` formatting only -- `stylua` for Lua formatting -- Nix (optional, via `flake.nix` for reproducible dev shell) - -**Production:** -- Self-contained binary; no runtime dependencies beyond system libc -- Platforms: Linux x86_64/aarch64, macOS x86_64/aarch64 (min 10.12), Windows x86_64/aarch64 -- jemalloc linked statically on Linux; system allocator on macOS/Windows -- Apple M1 optimized build via `-Ctarget-cpu=apple-m1` rustflag - ---- - -*Stack analysis: 2026-03-21* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md deleted file mode 100644 index 1d496d2b..00000000 --- a/.planning/codebase/STRUCTURE.md +++ /dev/null @@ -1,256 +0,0 @@ -# Codebase Structure - -**Analysis Date:** 2026-03-21 - -## Directory Layout - -``` -yazi/ # Workspace root -├── Cargo.toml # Workspace manifest, centralized deps, clippy rules -├── Cargo.lock # Lockfile (committed) -├── rustfmt.toml # Hard tabs, nightly formatting features -├── stylua.toml # Lua formatter config (for plugin files) -├── .cargo/config.toml # jemalloc global allocator, linker flags -├── assets/ # Icons, screenshots, misc assets -├── scripts/ # Dev utilities (icon validation, form scripts) -├── nix/ # Nix flake derivations -├── snap/ # Snapcraft packaging -├── .github/ # CI workflows, issue templates -├── .planning/ # GSD planning documents (not shipped) -│ -├── yazi-fm/ # BINARY: main file manager (default workspace member) -├── yazi-cli/ # BINARY: `ya` CLI tool (default workspace member) -│ -├── yazi-actor/ # Actor trait + all command implementations -├── yazi-adapter/ # Terminal image protocol adapters (kgp/sixel/iip/chafa/ueberzug) -├── yazi-binding/ # Lua ↔ Rust type bindings (mlua UserData) -├── yazi-boot/ # CLI arg parsing (clap), boot configuration -├── yazi-build/ # Build-time code generation helper binary -├── yazi-codegen/ # Proc-macro support crate -├── yazi-config/ # Config parsing (yazi.toml, keymap.toml, theme.toml) -├── yazi-core/ # Core state structs (Core, Mgr, Tab, Tasks, overlays) -├── yazi-dds/ # IPC pub/sub server (Unix socket, multi-instance) -├── yazi-emulator/ # Terminal emulator detection (brand, dimensions) -├── yazi-ffi/ # macOS FFI bindings (CoreFoundation, IOKit, DiskArbitration) -├── yazi-fs/ # Filesystem abstractions (File, Files, sorting, filtering) -├── yazi-macro/ # Proc-macros (act!, emit!, succ!, mod_pub!, mod_flat!, render!, ...) -├── yazi-packing/ # Plugin packaging utilities -├── yazi-parser/ # Options structs for every action (parsed from ActionCow) -├── yazi-plugin/ # Lua runtime, plugin loader, isolate sandboxing -├── yazi-proxy/ # Fire-and-forget action emitters for async contexts -├── yazi-scheduler/ # Task runner (file ops, preload, fetch, plugin, process workers) -├── yazi-sftp/ # SFTP client (russh-based remote filesystem) -├── yazi-shared/ # Shared primitives (Event, Layer, Id, RoCell, URL types, ...) -├── yazi-shim/ # Patches/shims for crossterm and ratatui -├── yazi-term/ # Ratatui Terminal wrapper with partial-render support -├── yazi-tty/ # Raw TTY handle (separate from ratatui for direct writes) -├── yazi-vfs/ # Virtual filesystem (local + SFTP provider abstraction) -└── yazi-watcher/ # Filesystem watcher (local inotify/FSEvents + remote polling) -``` - -## Directory Purposes - -**`yazi-fm/src/`:** -- Purpose: Binary crate for the main `yazi` process -- Contains: `main.rs`, `App` event loop, `Dispatcher`, `Router`, `Executor`, `Root` renderer, per-overlay UI widgets -- Key files: `yazi-fm/src/main.rs`, `yazi-fm/src/app/app.rs`, `yazi-fm/src/dispatcher.rs`, `yazi-fm/src/executor.rs`, `yazi-fm/src/router.rs`, `yazi-fm/src/root.rs` - -**`yazi-cli/src/`:** -- Purpose: Binary crate for the `ya` companion CLI -- Contains: `main.rs`, `args.rs`, command handlers for `emit`, `emit-to`, `pub`, `pub-to`, `sub`, `pkg` -- Key files: `yazi-cli/src/main.rs`, `yazi-cli/src/package/` - -**`yazi-actor/src/`:** -- Purpose: All command/action implementations; the `Actor` trait definition; `Ctx` context struct; `Lives` Lua scope manager -- Sub-directories: `app/` (15 actors), `mgr/` (~60 actors), `cmp/`, `confirm/`, `help/`, `input/`, `notify/`, `pick/`, `spot/`, `tasks/`, `which/`, `lives/`, `core/` -- Key files: `yazi-actor/src/actor.rs`, `yazi-actor/src/context.rs`, `yazi-actor/src/lives/lives.rs` - -**`yazi-core/src/`:** -- Purpose: Pure state structs; no I/O, no async; mutated only by actors via `Ctx` -- Sub-directories: `mgr/` (Mgr, Tabs, Yanked, Mimetype), `tab/` (Tab, Folder, Preview, Finder, Selected, History, Backstack, Mode), `tasks/`, `cmp/`, `confirm/`, `help/`, `input/`, `notify/`, `pick/`, `spot/`, `which/` -- Key files: `yazi-core/src/core.rs`, `yazi-core/src/mgr/mgr.rs`, `yazi-core/src/tab/tab.rs` - -**`yazi-config/src/`:** -- Purpose: TOML config parsing; exposes global `static` values (`YAZI`, `KEYMAP`, `THEME`, `LAYOUT`) -- Sub-directories: `keymap/`, `mgr/`, `open/`, `opener/`, `plugin/`, `popup/`, `preview/`, `tasks/`, `theme/`, `vfs/`, `which/` -- Key files: `yazi-config/src/lib.rs`, `yazi-config/preset/yazi-default.toml`, `yazi-config/preset/keymap-default.toml` - -**`yazi-config/preset/`:** -- Purpose: Embedded default configurations shipped with the binary -- Contains: `yazi-default.toml`, `keymap-default.toml`, `theme-dark.toml`, `theme-light.toml`, `vfs-default.toml` - -**`yazi-shared/src/`:** -- Purpose: Cross-crate primitives with no domain logic -- Contains: `event/` (Event enum, ActionCow), `url/` (UrlBuf, UrlLike, Scheme), `data/`, `errors/`, `pool/`, `strand/`, and misc types (`Id`, `Layer`, `Source`, `RoCell`, `SyncCell`, `Debounce`) -- Key files: `yazi-shared/src/event/event.rs`, `yazi-shared/src/layer.rs` - -**`yazi-dds/src/`:** -- Purpose: IPC broker for multi-instance communication -- Contains: `client.rs`, `server.rs`, `pubsub.rs`, `payload.rs`, `stream.rs`, `ember/` (message frame types: Hi, Hey, Bye, payload body) -- Key files: `yazi-dds/src/client.rs`, `yazi-dds/src/server.rs`, `yazi-dds/src/lib.rs` - -**`yazi-plugin/src/`:** -- Purpose: Lua 5.5 scripting engine initialization, plugin loader, coroutine/isolate sandboxing -- Sub-directories: `runtime/` (Lua globals composer), `loader/` (plugin discovery), `isolate/` (sandboxed execution), `pubsub/`, `fs/`, `external/`, `process/`, `theme/`, `utils/` -- Key files: `yazi-plugin/src/lua.rs`, `yazi-plugin/src/runtime/runtime.rs` - -**`yazi-binding/src/`:** -- Purpose: Lua UserData implementations for Rust types (Url, Rect, Style, Color, File, Id, Image, etc.) -- Contains: `elements/` (ratatui widget helpers), and flat modules for each bound type -- Key files: `yazi-binding/src/elements/`, `yazi-binding/src/file.rs`, `yazi-binding/src/url.rs` - -**`yazi-scheduler/src/`:** -- Purpose: Async task runner with priority queues -- Sub-directories: `fetch/`, `file/`, `hook/`, `plugin/`, `preload/`, `process/`, `size/` -- Key files: `yazi-scheduler/src/scheduler.rs`, `yazi-scheduler/src/runner.rs` - -**`yazi-fs/src/`:** -- Purpose: Local filesystem abstractions: `File`, `Files` collection, URL/path helpers, sorting, filtering, XDG paths -- Contains: `file.rs`, `files.rs`, `cha.rs` (file characteristics), `sorter.rs`, `filter.rs`, `mounts.rs`, `provider/` -- Key files: `yazi-fs/src/file.rs`, `yazi-fs/src/files.rs` - -**`yazi-vfs/src/`:** -- Purpose: Unified virtual filesystem provider interface; delegates to local or SFTP backend -- Contains: `provider/` with `local/`, `sftp/`; `cha.rs`, `file.rs`, `files.rs`, `op.rs` -- Key files: `yazi-vfs/src/provider/providers.rs`, `yazi-vfs/src/provider/provider.rs` - -**`yazi-adapter/src/`:** -- Purpose: Image rendering to terminal; auto-selects protocol per emulator brand -- Contains: `adapter.rs`, `adapters.rs`, `drivers/` (chafa, iip, kgp, kgp_old, sixel, ueberzug), `image.rs`, `info.rs` -- Key files: `yazi-adapter/src/adapter.rs`, `yazi-adapter/src/adapters.rs` - -**`yazi-macro/src/`:** -- Purpose: Proc-macros used throughout the workspace -- Contains: `actor.rs` (`act!`), `event.rs` (`emit!`, `relay!`), `render.rs` (`render!`), `module.rs` (`mod_pub!`, `mod_flat!`), `context.rs`, `fs.rs`, `fmt.rs`, `log.rs`, `stdio.rs`, `platform.rs` - -**`yazi-proxy/src/`:** -- Purpose: Thin wrappers allowing async tasks to emit actions without holding `Core` -- Contains: one file per layer: `app.rs`, `mgr.rs`, `input.rs`, `cmp.rs`, `tasks.rs`, `notify.rs`, `pick.rs`, `which.rs`, `confirm.rs` - -**`yazi-parser/src/`:** -- Purpose: Typed `Options` structs (`CdOpt`, `OpenOpt`, `SortOpt`, ...) deserialized from `ActionCow` parameters -- Sub-directories: one per layer matching `yazi-actor` structure - -**`yazi-watcher/src/`:** -- Purpose: Watch local directory changes (platform FS events) and poll remote paths -- Sub-directories: `local/` (OS watcher backend), `remote/` (polling for VFS paths) - -## Key File Locations - -**Entry Points:** -- `yazi-fm/src/main.rs`: `yazi` binary entry; subsystem initialization order -- `yazi-cli/src/main.rs`: `ya` CLI entry - -**Event Bus:** -- `yazi-shared/src/event/event.rs`: `Event` enum definition and global channel - -**Core State:** -- `yazi-core/src/core.rs`: `Core` struct aggregating all UI component states -- `yazi-core/src/mgr/mgr.rs`: File manager state (tabs, yanked, watcher) -- `yazi-core/src/tab/tab.rs`: Per-tab state (current folder, history, selection, preview) - -**Action Dispatch:** -- `yazi-fm/src/executor.rs`: Routes `ActionCow` by layer to `act!` macros -- `yazi-fm/src/router.rs`: Translates key events to action sequences via `KEYMAP` -- `yazi-actor/src/actor.rs`: `Actor` trait definition - -**Actor Context:** -- `yazi-actor/src/context.rs`: `Ctx` struct — short-lived mutable `Core` borrow passed to actors - -**Rendering:** -- `yazi-fm/src/root.rs`: `Root` widget — Lua-driven base + Rust overlay z-stack -- `yazi-fm/src/app/render.rs`: `App::render()` and `App::render_partially()` - -**Configuration:** -- `yazi-config/src/lib.rs`: Global statics `YAZI`, `KEYMAP`, `THEME`, `LAYOUT` -- `yazi-config/preset/`: Embedded default TOML files - -**IPC:** -- `yazi-dds/src/client.rs`: Client connect/reconnect loop and `shot()`/`draw()` for `ya` -- `yazi-dds/src/server.rs`: Unix socket server routing messages by ability - -**Lua Integration:** -- `yazi-actor/src/lives/lives.rs`: `Lives::scope()` — pins Rust state as Lua globals -- `yazi-plugin/src/runtime/runtime.rs`: `cx` global composer (args, mgr, preview, tasks) - -**Testing:** -- Tests are co-located within each crate under `src/` using `#[cfg(test)]` modules - -## Naming Conventions - -**Files:** -- `snake_case.rs` for all Rust source files -- `mod.rs` for module root files within subdirectories -- `lib.rs` for crate root files - -**Directories:** -- `snake_case` for all source subdirectories -- Directory name matches the module name it exposes (e.g., `yazi-actor/src/mgr/` → `pub mod mgr`) - -**Crates:** -- All crates prefixed with `yazi-` (hyphenated); Rust module names use `yazi_` (underscored) - -**Types:** -- `PascalCase` for structs, enums, traits -- `SCREAMING_SNAKE_CASE` for module-level statics (`YAZI`, `KEYMAP`, `TX`, `RX`) -- `snake_case` for functions and variables - -**Macros:** -- `snake_case!` for all proc-macros (`act!`, `emit!`, `succ!`, `render!`, `mod_pub!`) - -## Where to Add New Code - -**New actor (user command):** -- Implementation: `yazi-actor/src/{layer}/{command_name}.rs` implementing `Actor` trait -- Options struct: `yazi-parser/src/{layer}/{command_name}.rs` or add to existing file -- Registration: Add to `yazi-fm/src/executor.rs` in the relevant layer method using `on!(command_name)` -- Proxy helper (if needed from async): Add method to `yazi-proxy/src/{layer}.rs` - -**New UI overlay:** -- State struct: `yazi-core/src/{overlay_name}/` with `mod.rs` -- Add field to `Core` in `yazi-core/src/core.rs` -- Actor implementations: `yazi-actor/src/{overlay_name}/` -- Renderer widget: `yazi-fm/src/{overlay_name}/` implementing `ratatui::Widget` -- Add `Layer` variant to `yazi-shared/src/layer.rs` -- Add executor routing in `yazi-fm/src/executor.rs` -- Register in `Core::layer()` priority chain in `yazi-core/src/core.rs` - -**New configuration section:** -- Config struct: `yazi-config/src/{section}/` -- Add field to the relevant config aggregate struct -- Add default values to `yazi-config/preset/yazi-default.toml` - -**New Lua binding (expose Rust type to plugins):** -- UserData impl: `yazi-binding/src/{type_name}.rs` -- Add to `yazi-actor/src/lives/` if it needs to be accessible via the `cx` global - -**Shared utilities:** -- Cross-crate primitives: `yazi-shared/src/` -- Filesystem helpers: `yazi-fs/src/` -- New proc-macro: `yazi-macro/src/` - -## Special Directories - -**`yazi-config/preset/`:** -- Purpose: Default TOML configurations embedded into the binary at build time -- Generated: No (hand-authored) -- Committed: Yes - -**`.planning/`:** -- Purpose: GSD planning documents and codebase analysis (not shipped) -- Generated: By GSD tooling -- Committed: No (gitignored or team-local) - -**`target/`:** -- Purpose: Cargo build artifacts -- Generated: Yes -- Committed: No - -**`assets/`:** -- Purpose: Non-code resources (icon files, screenshots) -- Generated: No -- Committed: Yes - ---- - -*Structure analysis: 2026-03-21* diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md deleted file mode 100644 index 6829e349..00000000 --- a/.planning/codebase/TESTING.md +++ /dev/null @@ -1,251 +0,0 @@ -# Testing Patterns - -**Analysis Date:** 2026-03-21 - -## Test Framework - -**Runner:** -- Rust's built-in `cargo test` -- No external test runner; no `jest.config` or `vitest.config` - -**Assertion Library:** -- `assert_eq!`, `assert_ne!`, `assert!` from Rust standard library -- `anyhow::Result` used as test return type for `?` propagation - -**Run Commands:** -```bash -cargo test --workspace --verbose # Run all tests across all crates -cargo test -p yazi-core # Test a specific crate -cargo test test_backstack # Run a single test by name -cargo test --workspace # Full suite (CI equivalent) -``` - -## Test File Organization - -**Location:** -- Co-located: tests live in a `#[cfg(test)] mod tests { ... }` block at the bottom of the source file they test -- No separate `tests/` integration test directories found -- No external test fixtures directory - -**Naming:** -- Test module: always named `mod tests` -- Test functions: `test_` prefix followed by the function or concept being tested - - `fn test_backstack()`, `fn test_natsort()`, `fn test_clean_url()`, `fn test_split()` - - Behavior-specific names also used without `test_` prefix: `fn insert_many_success()`, `fn test_insert_conflicting_parent()` - -**Structure:** -``` -src/ -├── some_module.rs # Implementation + inline test module -│ └── #[cfg(test)] mod tests { ... } -├── path/ -│ ├── clean.rs # Tests at bottom of file -│ └── relative.rs # Tests at bottom of file -``` - -## Test Structure - -**Suite Organization:** -```rust -#[cfg(test)] -mod tests { - use super::*; // Always import everything from parent - - #[test] - fn test_something() { - // arrange - let mut subject = Subject::default(); - // act + assert - assert_eq!(subject.method(), expected); - } - - #[test] - fn test_something_else() -> anyhow::Result<()> { - // Use Result return type for tests needing ? propagation - let u: UrlBuf = "/some/path".parse()?; - assert_eq!(format!("{u:?}"), "/some/path"); - Ok(()) - } -} -``` - -**Section Separator:** -```rust -// --- Tests -#[cfg(test)] -mod tests { -``` -The `// --- Tests` separator comment appears above the test module in some files. - -**Patterns:** -- Setup: `Subject::default()` for zero-state; `yazi_shared::init_tests()` for global state -- No `before_each` / `after_each`; each test is fully self-contained -- Data tables (slices of tuples) used for parameterized assertions -- Helper functions within the `mod tests` block used to reduce assertion boilerplate - -## Mocking - -**Framework:** None – no mocking crate used - -**Patterns:** -- No mock objects; tests call real implementations directly -- Platform-specific tests gated with `#[cfg(unix)]` / `#[cfg(windows)]` on individual test functions -- Global state initialized with `yazi_shared::init_tests()` where needed (see below) -- Tests that require filesystem state initialize `yazi_fs::init()` directly - -**What to Mock:** -- Nothing – the codebase uses no mocking framework - -**What NOT to Mock:** -- Filesystem, URLs, sorting – all tested with real values - -## Fixtures and Factories - -**Test Data:** -The dominant pattern is inline slice literals of tuples serving as parameterized test cases: - -```rust -let cases = [ - // Comment describing the category - ("/a/b", "/a/b/c", "c"), - ("/a/b/c", "/a/b", ".."), - ("/a/b/d", "/a/b/c", "../c"), -]; -for (from, to, expected) in cases { - let result = function_under_test(from, to); - assert_eq!(result, expected); -} -``` - -**Helper closures and functions inside test modules:** -```rust -fn cmp(left: &[&str]) { - let mut right = left.to_vec(); - right.sort_by(|a, b| natsort(a.as_bytes(), b.as_bytes(), true)); - assert_eq!(left, right); -} - -fn matches(glob: &str, url: &str) -> bool { - Pattern::from_str(glob).unwrap().match_url(UrlCow::try_from(url).unwrap(), false) -} -``` - -**Global state initialization:** -```rust -#[test] -fn test_something() { - yazi_shared::init_tests(); // Must call before any URL/path parsing - yazi_fs::init(); // Must call before CWD-dependent tests - // ... -} -``` -`init_tests()` is idempotent via `OnceLock` so ordering between tests doesn't matter. - -**Location:** -- No separate fixtures directory; all test data is inlined in the test block - -## Coverage - -**Requirements:** Not enforced – no minimum coverage threshold configured - -**View Coverage:** -```bash -# No coverage tooling configured in the repository. -# Use cargo-llvm-cov or cargo-tarpaulin manually if needed: -cargo llvm-cov --workspace -``` - -## Test Types - -**Unit Tests:** -- Dominant form: all tests are unit tests co-located with the code they test -- Scope: individual functions and struct methods -- Approach: pure input/output; no I/O or side effects except where `init_tests()` sets up global state - -**Integration Tests:** -- Not present as a separate category; complex behavior tested via unit tests against real types - -**E2E Tests:** -- Not present - -**Property-based Tests:** -- Not present (no `proptest` or `quickcheck` dependencies) - -## Common Patterns - -**Table-driven tests (most common):** -```rust -let cases: &[(&str, &str)] = &[ - ("input_a", "expected_a"), - ("input_b", "expected_b"), -]; -for &(input, expected) in cases { - assert_eq!(function(input), expected, "input: {:?}", input); -} -``` - -**Fallible tests with `anyhow::Result`:** -```rust -#[test] -fn test_join() -> anyhow::Result<()> { - crate::init_tests(); - let base: UrlBuf = "/a".parse()?; - assert_eq!(format!("{:?}", base.try_join("b/c")?), "/a/b/c"); - Ok(()) -} -``` - -**Platform-conditional tests:** -```rust -#[cfg(unix)] -#[test] -fn test_split() { - yazi_shared::init_tests(); - yazi_fs::init(); - // unix-specific assertions -} - -#[cfg(windows)] -#[test] -fn test_split() { - yazi_fs::init(); - // windows-specific assertions -} -``` - -**Struct state verification tests:** -```rust -#[test] -fn test_remove() { - let mut s = Selected::default(); - assert!(s.add(Path::new("/a/b"))); - assert!(!s.remove(Path::new("/a/c"))); // non-existent - assert!(s.remove(Path::new("/a/b"))); // exists - assert!(s.inner.is_empty()); - assert!(s.parents.is_empty()); -} -``` -Note: test code accesses private fields (`s.inner`, `s.stack`, `s.cursor`) directly because tests are in the same file. - -**Error testing:** -```rust -// Testing absence (None/false) explicitly -assert_eq!(bs.shift_backward(), None); -assert!(!s.add(Path::new("/a/b"))); // returns false when conflict - -// Testing error propagation -let result = risky_function(); -assert!(result.is_err()); -// OR via ? in Result-returning tests -``` - -## CI Integration - -**Matrix:** Ubuntu, Windows, macOS (all via `cargo test --workspace --verbose`) -**Trigger:** push/PR to `main` branch -**Lint checks run separately:** Clippy (`cargo clippy --all`), rustfmt (`rustfmt +nightly --check **/*.rs`), StyLua -**Caching:** sccache via `mozilla-actions/sccache-action` - ---- - -*Testing analysis: 2026-03-21* diff --git a/.planning/phases/01-implementation/01-01-PLAN.md b/.planning/phases/01-implementation/01-01-PLAN.md deleted file mode 100644 index 4eaf82f3..00000000 --- a/.planning/phases/01-implementation/01-01-PLAN.md +++ /dev/null @@ -1,417 +0,0 @@ ---- -phase: 01-implementation -plan: 01 -type: execute -wave: 1 -depends_on: [] -files_modified: - - yazi-adapter/Cargo.toml - - yazi-adapter/src/image.rs -autonomous: true -requirements: - - RESIZE-01 - - RESIZE-02 - - RESIZE-03 - - RESIZE-04 - - RESIZE-05 - - COMPAT-01 - - COMPAT-02 - - COMPAT-03 - - COMPAT-04 - -must_haves: - truths: - - "Image downscaling uses linear-light pipeline (sRGB forward map before resize, backward map after)" - - "All 5 config filter strings map to correct fast_image_resize ResizeAlg variants" - - "Unknown filter names log a warning and fall back to lanczos3" - - "Nearest-neighbor resize skips linearization (no wasted buffer allocations)" - - "DynamicImage variant (Luma8, LumaA8, Rgb8, Rgba8) is preserved after resize" - - "Both precache() and downscale() use the new fir_resize path" - - "precache() public signature is unchanged" - - "All resize work remains inside spawn_blocking closures" - artifacts: - - path: "yazi-adapter/Cargo.toml" - provides: "fast_image_resize dependency" - contains: "fast_image_resize" - - path: "yazi-adapter/src/image.rs" - provides: "Linear-light resize implementation" - exports: ["Image"] - contains: "fir_resize" - key_links: - - from: "yazi-adapter/src/image.rs" - to: "fast_image_resize crate" - via: "use fast_image_resize::{...}" - pattern: "create_srgb_mapper|Resizer::new|ResizeOptions" - - from: "yazi-adapter/src/image.rs::precache()" - to: "fir_resize helper" - via: "Self::fir_resize call replacing img.resize()" - pattern: "Self::fir_resize" - - from: "yazi-adapter/src/image.rs::downscale()" - to: "fir_resize helper" - via: "Self::fir_resize call replacing img.resize()" - pattern: "Self::fir_resize" ---- - - -Replace both image resize call sites in yazi-adapter with linear-light-correct downscaling via fast_image_resize. - -Purpose: The current `image` crate `resize()` convolves in gamma-encoded sRGB space, producing visibly blurry thumbnails. Switching to `fast_image_resize` with explicit sRGB linearization (forward_map before resize, backward_map after) produces correct linear-light convolution with SIMD acceleration. - -Output: Modified `yazi-adapter/Cargo.toml` (new dependency) and `yazi-adapter/src/image.rs` (new resize pipeline replacing both call sites). - - - -@~/.claude/get-shit-done/workflows/execute-plan.md -@~/.claude/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/01-implementation/01-CONTEXT.md -@.planning/phases/01-implementation/01-RESEARCH.md - - - - -From yazi-adapter/src/image.rs (CURRENT — to be modified): -```rust -pub struct Image; - -impl Image { - // PUBLIC — signature MUST NOT change (called by Lua plugin layer) - pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> - - // pub(super) — called by all protocol drivers - pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result - - // PRIVATE — to be replaced with resize_alg() - fn filter() -> FilterType - - // Unchanged helpers - pub(super) fn max_pixel(rect: Rect) -> (u16, u16) - pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect - async fn decode_from(path: PathBuf) -> Result<(DynamicImage, Orientation)> - fn flip_size(orientation: Orientation, (w, h): (u16, u16)) -> (u32, u32) -} -``` - -From yazi-adapter/src/icc.rs (UNCHANGED — produces input to resize): -```rust -pub(super) struct Icc; -impl Icc { - // Can output: GrayImage (Luma8), GrayAlphaImage (LumaA8), RgbImage (Rgb8), RgbaImage (Rgba8) - pub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result -} -``` - -From yazi-config/src/preview/preview.rs (UNCHANGED — config source): -```rust -pub struct Preview { - pub image_filter: String, // "nearest", "triangle", "catmull-rom", "gaussian", "lanczos3" - pub image_quality: u8, - pub max_width: u16, - pub max_height: u16, -} -``` - - - - - - - Task 1: Add fast_image_resize dependency - yazi-adapter/Cargo.toml - - - yazi-adapter/Cargo.toml - - -Add `fast_image_resize` to the `[dependencies]` section of `yazi-adapter/Cargo.toml`. - -Insert the following line in the external dependencies block (after the `image` line, maintaining alphabetical order among external deps): - -```toml -fast_image_resize = { version = "6.0.0", features = ["image"] } -``` - -The `image` feature enables zero-copy `IntoImageView`/`IntoImageViewMut` bridge for `DynamicImage` and all `ImageBuffer` variants. Do NOT add `rayon` feature (yazi uses `spawn_blocking` for parallelism). Do NOT add `only_u8x4` feature (it would disable Gray U8 and GrayAlpha U8x2 support). - -No workspace-level entry in root `Cargo.toml` is needed — only `yazi-adapter` uses this library. - - - cd /Users/D051699/git/playground/yazi && grep 'fast_image_resize' yazi-adapter/Cargo.toml && cargo check -p yazi-adapter 2>&1 | tail -5 - - - - yazi-adapter/Cargo.toml contains line: `fast_image_resize = { version = "6.0.0", features = ["image"] }` - - `cargo check -p yazi-adapter` exits 0 - - fast_image_resize 6.0.0 is declared as a dependency of yazi-adapter with the image feature enabled - - - - Task 2: Replace resize pipeline with linear-light fast_image_resize - yazi-adapter/src/image.rs - - - yazi-adapter/src/image.rs - - yazi-adapter/src/icc.rs - - .planning/phases/01-implementation/01-RESEARCH.md - - -Rewrite `yazi-adapter/src/image.rs` to replace the `image` crate's `img.resize()` with `fast_image_resize` using explicit sRGB linearization. The file has 128 lines currently. The changes are: - -**1. Update imports (line 4):** - -Remove `imageops::FilterType` from the `image` use statement. Add new imports for `fast_image_resize`: - -```rust -use fast_image_resize::{IntoImageView, ResizeAlg, ResizeOptions, Resizer, create_srgb_mapper}; -use fast_image_resize::images::Image as FirImage; -``` - -The full `image` import line becomes (remove `imageops::FilterType`): -```rust -use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, metadata::Orientation}; -``` - -**2. Replace `fn filter()` (lines 86-95) with `fn resize_alg()`:** - -Per D-01 (default lanczos3), D-02 (same 5 filter names), D-03 (warn + lanczos3 fallback for unknown): - -```rust -fn resize_alg() -> ResizeAlg { - use fast_image_resize::FilterType; - match YAZI.preview.image_filter.as_str() { - "nearest" => ResizeAlg::Nearest, - "triangle" => ResizeAlg::Convolution(FilterType::Bilinear), - "catmull-rom" => ResizeAlg::Convolution(FilterType::CatmullRom), - "gaussian" => ResizeAlg::Convolution(FilterType::Gaussian), - "lanczos3" => ResizeAlg::Convolution(FilterType::Lanczos3), - other => { - tracing::warn!("unknown image_filter {other:?}, falling back to lanczos3"); - ResizeAlg::Convolution(FilterType::Lanczos3) - } - } -} -``` - -Note: `"triangle"` maps to `FilterType::Bilinear` (the `image` crate's `Triangle` IS bilinear interpolation; `fast_image_resize` uses the name `Bilinear`). - -**3. Add `fn fir_resize()` private helper:** - -This is the core of the change. It implements: sRGB linearize -> resize in linear light -> sRGB encode back. For `ResizeAlg::Nearest`, skip linearization (nearest doesn't convolve, so the mapper is wasted work). - -Handle `pixel_type()` returning `None` (exotic 16-bit/float variants from `DynamicImage::from_decoder()` fallback in icc.rs) by converting to `to_rgb8()` first with a `tracing::debug!` log. - -```rust -fn fir_resize(img: DynamicImage, w: u32, h: u32, alg: ResizeAlg) -> anyhow::Result { - let img = match img.pixel_type() { - Some(_) => img, - None => { - tracing::debug!("converting exotic pixel type to rgb8 for resize"); - img.to_rgb8().into() - } - }; - - let pixel_type = img - .pixel_type() - .ok_or_else(|| anyhow::anyhow!("unsupported pixel type for fast_image_resize"))?; - - if matches!(alg, ResizeAlg::Nearest) { - let mut dst = FirImage::new(w, h, pixel_type); - let mut resizer = Resizer::new(); - resizer.resize(&img, &mut dst, &ResizeOptions::new().resize_alg(alg))?; - return Self::reconstruct(w, h, pixel_type, dst.into_vec()); - } - - let mapper = create_srgb_mapper(); - - // sRGB -> linear - let mut linear_src = FirImage::new(img.width(), img.height(), pixel_type); - mapper.forward_map(&img, &mut linear_src)?; - - // Resize in linear light (mul_div_alpha=true by default handles alpha premult) - let mut dst = FirImage::new(w, h, pixel_type); - let mut resizer = Resizer::new(); - let opts = ResizeOptions::new().resize_alg(alg); - resizer.resize(&linear_src, &mut dst, &opts)?; - - // linear -> sRGB - let mut srgb_dst = FirImage::new(w, h, pixel_type); - mapper.backward_map(&dst, &mut srgb_dst)?; - - Self::reconstruct(w, h, pixel_type, srgb_dst.into_vec()) -} -``` - -**4. Add `fn reconstruct()` private helper:** - -Reconstructs the correct `DynamicImage` variant from the resized buffer bytes, matching the original pixel type. This preserves COMPAT-02 (buffer format preserved). - -```rust -fn reconstruct( - w: u32, - h: u32, - pixel_type: fast_image_resize::PixelType, - buf: Vec, -) -> anyhow::Result { - use anyhow::Context; - use fast_image_resize::PixelType; - use image::{GrayAlphaImage, GrayImage, RgbImage, RgbaImage}; - match pixel_type { - PixelType::U8 => { - Ok(GrayImage::from_raw(w, h, buf).context("gray reconstruct")?.into()) - } - PixelType::U8x2 => { - Ok(GrayAlphaImage::from_raw(w, h, buf).context("graya reconstruct")?.into()) - } - PixelType::U8x3 => { - Ok(RgbImage::from_raw(w, h, buf).context("rgb reconstruct")?.into()) - } - PixelType::U8x4 => { - Ok(RgbaImage::from_raw(w, h, buf).context("rgba reconstruct")?.into()) - } - _ => Err(anyhow::anyhow!("unexpected pixel type {pixel_type:?}")), - } -} -``` - -**5. Update `precache()` call site (line 21):** - -Replace: -```rust -img = img.resize(w, h, Self::filter()); -``` -With: -```rust -img = Self::fir_resize(img, w, h, Self::resize_alg())?; -``` - -The `?` operator works here because the closure already returns `Ok::<_, ImageError>(buf)` -- but note the error type changes. The closure return type must be updated from `Ok::<_, ImageError>(buf)` to `Ok::<_, anyhow::Error>(buf)` since `fir_resize` returns `anyhow::Result`. The outer `.await??` still works because `anyhow::Error` implements the required traits. - -Actually, looking more carefully: the spawn_blocking closure currently returns `Result, ImageError>`. Since `fir_resize` returns `anyhow::Result`, and the `?` operator on it needs the closure to return a compatible error type, change the closure to return `anyhow::Result>`: -- Change `Ok::<_, ImageError>(buf)` to `Ok::<_, anyhow::Error>(buf)` (or just `Ok(buf)` if the compiler can infer) -- The outer `precache` function already returns `anyhow::Result<()>`, and `.await??` will work with `anyhow::Error` from the inner closure - -**6. Update `downscale()` call site (line 54):** - -Replace: -```rust -img = img.resize(w, h, Self::filter()) -``` -With: -```rust -img = Self::fir_resize(img, w, h, Self::resize_alg())?; -``` - -The current `spawn_blocking` closure in `downscale()` returns `DynamicImage` directly (no Result). Since `fir_resize` returns `anyhow::Result`, update the closure to return `anyhow::Result`: -- Wrap the final `img` return in `Ok(img)` -- Change `.await?` to `.await??` (first `?` unwraps JoinError, second unwraps anyhow::Error) -- The outer function already returns `Result` so this works - -The updated `downscale` spawn_blocking block becomes: -```rust -let img = tokio::task::spawn_blocking(move || { - if img.width() > w || img.height() > h { - img = Self::fir_resize(img, w, h, Self::resize_alg())?; - } - if orientation != Orientation::NoTransforms { - img.apply_orientation(orientation); - } - Ok(img) -}) -.await??; -``` - -**Important constraints:** -- Keep all new code inside the existing `spawn_blocking` closures (COMPAT-03) -- Do NOT change `precache()` public signature `pub async fn precache(src: PathBuf, cache: &Path) -> Result<()>` (COMPAT-04) -- Use hard tabs for indentation per rustfmt.toml -- Follow import grouping: std -> external crates -> workspace crates - - - cd /Users/D051699/git/playground/yazi && cargo build -p yazi-adapter 2>&1 | tail -10 && cargo clippy -p yazi-adapter 2>&1 | tail -10 - - - - yazi-adapter/src/image.rs does NOT contain `imageops::FilterType` - - yazi-adapter/src/image.rs contains `fn resize_alg() -> ResizeAlg` - - yazi-adapter/src/image.rs contains `fn fir_resize(img: DynamicImage, w: u32, h: u32, alg: ResizeAlg)` - - yazi-adapter/src/image.rs contains `fn reconstruct(` - - yazi-adapter/src/image.rs contains `create_srgb_mapper()` - - yazi-adapter/src/image.rs contains `mapper.forward_map(` and `mapper.backward_map(` - - yazi-adapter/src/image.rs contains `matches!(alg, ResizeAlg::Nearest)` (skip linearization for nearest) - - yazi-adapter/src/image.rs contains `tracing::warn!("unknown image_filter` (D-03 fallback) - - yazi-adapter/src/image.rs contains `Self::fir_resize(img, w, h, Self::resize_alg())` in precache() - - yazi-adapter/src/image.rs contains `Self::fir_resize(img, w, h, Self::resize_alg())` in downscale() - - yazi-adapter/src/image.rs filter mapping contains `"triangle" => ResizeAlg::Convolution(FilterType::Bilinear)` - - precache() signature is still `pub async fn precache(src: PathBuf, cache: &Path) -> Result<()>` - - `cargo build -p yazi-adapter` exits 0 - - `cargo clippy -p yazi-adapter` produces no errors - - Both resize call sites use fir_resize with linear-light pipeline; all 5 filter names map correctly; unknown filters warn and fallback to lanczos3; DynamicImage variant is preserved; exotic pixel types handled gracefully - - - - Task 3: Full workspace build and lint verification - yazi-adapter/src/image.rs - - - yazi-adapter/src/image.rs - - -Run full workspace build and clippy to verify no regressions across the entire codebase. - -```bash -cargo build --release 2>&1 -cargo clippy --all 2>&1 -cargo test --workspace 2>&1 -``` - -If clippy produces warnings in the modified code, fix them: -- Common issues: unused imports (the old `FilterType` import), missing `use Self` pattern, format_push_string -- If `IntoImageView` import triggers an "unused import" warning because it is only used as a trait impl (not called directly), add `#[allow(unused_imports)]` or verify it is needed for the `.pixel_type()` method resolution - -If the release build fails, check for: -- Missing `Send` bounds on types used across `spawn_blocking` (all `fast_image_resize` types are `Send`) -- LTO compilation issues (unlikely with pure Rust dependency) - - - cd /Users/D051699/git/playground/yazi && cargo build --release 2>&1 | tail -5 && cargo clippy --all 2>&1 | tail -5 && cargo test --workspace 2>&1 | tail -10 - - - - `cargo build --release` exits 0 - - `cargo clippy --all` exits 0 with no new warnings in yazi-adapter - - `cargo test --workspace` exits 0 - - Full workspace builds and passes clippy without new warnings; all existing tests pass; release build with LTO succeeds - - - - - -After all tasks complete, verify the full implementation: - -1. **Dependency check:** `grep 'fast_image_resize' yazi-adapter/Cargo.toml` shows the dependency -2. **No old resize:** `grep 'img.resize(' yazi-adapter/src/image.rs` returns no matches (both call sites replaced) -3. **No old FilterType:** `grep 'imageops::FilterType' yazi-adapter/src/image.rs` returns no matches -4. **Linear pipeline present:** `grep 'forward_map\|backward_map' yazi-adapter/src/image.rs` shows both mapper calls -5. **Nearest optimization:** `grep 'Nearest' yazi-adapter/src/image.rs` shows the skip-linearization branch -6. **Filter mapping complete:** `grep -c 'ResizeAlg' yazi-adapter/src/image.rs` shows multiple usages -7. **Signature preserved:** `grep 'pub async fn precache' yazi-adapter/src/image.rs` shows unchanged signature -8. **Build clean:** `cargo build --release && cargo clippy --all && cargo test --workspace` all pass - - - -1. `yazi-adapter/src/image.rs` uses `fast_image_resize` with `create_srgb_mapper().forward_map()` before resize and `backward_map()` after (RESIZE-01, RESIZE-02) -2. Both `precache()` and `downscale()` call `fir_resize` instead of `img.resize()` (RESIZE-03) -3. All 5 filter strings correctly mapped to ResizeAlg variants (RESIZE-04) -4. Default path uses `Lanczos3` convolution in linear light (RESIZE-05) -5. No driver files modified (COMPAT-01) -6. `reconstruct()` preserves DynamicImage variant matching input pixel type (COMPAT-02) -7. All resize code inside `spawn_blocking` closures (COMPAT-03) -8. `precache()` public signature unchanged (COMPAT-04) -9. `cargo build --release` and `cargo clippy --all` pass clean - - - -After completion, create `.planning/phases/01-implementation/01-01-SUMMARY.md` -