diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9e09175 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,114 @@ +# logViewer + +High-performance log file viewer. Rust workspace with 4 crates, edition 2024, MSRV 1.92 (pinned in `rust-toolchain.toml`). + +## Workspace Layout + +``` +crates/core — log-viewer-core (library) — I/O, parsing, types, config, file watching +crates/tui — log-viewer-tui (binary) — terminal UI (ratatui + crossterm) +crates/gui — log-viewer-gui (binary) — GUI (egui + eframe) — STUB, not yet functional +crates/bench — log-viewer-bench (binary) — mmap vs pread benchmark harness +``` + +`core` owns all I/O, parsing, data types, and file watching. TUI/GUI consume core's public API. GUI is a placeholder with no core integration yet. + +**`default-members = ["crates/core"]`** — bare `cargo run` / `cargo test` target *core*, not the TUI. Always pass `-p log-viewer-tui` (or `--workspace`) explicitly. + +## Commands + +```bash +# Run the TUI (primary interface) +cargo run -p log-viewer-tui -- + +# Build/check/test/lint the whole workspace +cargo check --workspace +cargo test --workspace +cargo fmt --check --all +cargo clippy --workspace -- -D warnings + +# Single crate +cargo test -p log-viewer-core +cargo test -p log-viewer-tui + +# Benchmarks (test file is NOT auto-generated — see "Benchmarks" section below) +cargo run -p log-viewer-bench +cargo run -p log-viewer-bench -- --quick --suites startup,render --output results.md +``` + +## CI Gate (must pass before merge) + +Runs on ubuntu-latest + windows-latest (`.github/workflows/ci.yml`): +1. `cargo fmt --check --all` +2. `cargo check --workspace` +3. `cargo test --workspace` +4. `cargo clippy --workspace -- -D warnings` + +Toolchain installed via `dtolnay/rust-toolchain@stable` reading `rust-toolchain.toml` (channel `1.92`). No rustfmt.toml or clippy.toml — uses toolchain defaults. + +## Core Architecture + +All ten modules are declared `pub` in `lib.rs` and re-exported as `log_viewer_core::`. + +### Key Modules + +- **`types`** — `LogLevel`, `LogEntry`, `SearchQuery`, `SearchFilter`, `Bookmark`, `FileSession`, `DuplicateKey`, `SearchResult` +- **`error`** — `CoreError` (**12 variants** via thiserror: Io, Parse, Search, Index, Config, TomlSerialize, Encoding, Watch, Mmap, Cache, FileNotFound, Other) + `Result` alias. `From` impls for `io::Error` / `serde_json::Error` / `toml::de::Error` / `toml::ser::Error` / `notify::Error` mean `?` works freely. +- **`io::file_reader`** — mmap-backed `FileReader` with TOCTOU mitigation (post-mmap stat check), append/reload detection +- **`io::progressive_reader`** — `ProgressiveFileReader` state machine (`ReaderState::Sampling { .. } | Ready { .. }`), background indexer via crossbeam-channel, `VisualHeightIndex` for wrapped-line scroll +- **`io::line_index`** — Sparse index (every `BLOCK_SIZE = 256` lines, memchr SIMD). Serializable to disk. +- **`io::index_cache`** — Persistent cache with xxh3 content hash, atomic writes via temp files +- **`io::line_sampler`** / **`io::cache_util`** — shared helpers used by the index path +- **`io::wrap`** — Unicode-aware line wrapping (CJK/emoji/tab), JSON pretty-printing; enforces `MAX_WRAP_INPUT_LEN` +- **`io::read_cache`** — LRU read cache (not yet integrated into the live reader; future pread backend) +- **`parser::json`** — NDJSON parser with BOM handling, duplicate key detection +- **`parser::level`** — JSON-first level detection (`detect_level`); falls back to bounded keyword scan with word-boundary check on non-JSON lines +- **`watcher::file_watcher`** — File event watcher (notify + crossbeam) with append/truncate/rotation detection +- **`config`** — `ColorConfig` TOML load/save with per-field serde defaults + +### Stubs (single-line TODOs — planned, not yet built) + +- `filter::Filter` +- `bookmark::BookmarkManager` +- `session::SessionManager` +- `search::engine::SearchEngine` + +## TUI Architecture + +- **`main.rs`** — clap CLI (`files: Vec`), `TerminalGuard` RAII for raw mode + alternate screen. **CRITICAL**: `Drop` does not run on `std::process::exit` or `panic = "abort"` — never call `process::exit` inside the guarded scope; return `Err` and use `?` instead. Event loop: poll indexer → poll watcher → draw → poll keys, 100ms timeout. +- **`app.rs`** — `App` struct (~4000 lines including ~135 inline tests, test mod starts at `#[cfg(test)] mod tests`). `AppLoadingState: Empty | Loading { reader, estimated_lines, progress_percent } | Ready { reader } | Error(String)`. `AppMode: Normal | Settings`. Viewport cache for scroll. All key handling. +- **`ui.rs`** — ratatui rendering: title bar, content area, status bar, settings popup +- **`color.rs`** — `LogLevel` → ratatui `Color` via `ColorConfig` + +### TUI Keybindings + +j/k scroll, Ctrl+d/u half-page, Ctrl+f/b full-page, G/gg jump end/top, Tab toggle JSON formatting, S settings panel, q/Esc quit. Settings: j/k select level, ←/→ cycle colors, Enter save, Esc cancel. + +## Testing + +All tests are inline (`#[cfg(test)] mod tests` blocks). No `tests/` directories, no integration tests, no async tests. + +- **~450 tests total**: core 251, tui 136, bench 63, gui 0 +- Temp-file helpers vary per module — check the local `tests` mod before assuming an API: + - `make_temp_file(content) -> PathBuf` — `tui/app.rs`, `tui/ui.rs` + - `make_test_file(lines) -> NamedTempFile` — `core/io/index_cache.rs` + - `make_file(data) -> NamedTempFile` — `core/io/read_cache.rs` + - `struct TempFile { ... }` — `core/io/line_sampler.rs` +- `insta` is a declared dev-dependency but currently **unused** (no `insta::` calls anywhere in the workspace) +- TUI render tests use ratatui `TestBackend` (see `tui/ui.rs`) +- Bench crate has its own unit tests for mmap/pread backends, metrics, and data_gen helpers + +## Benchmarks + +Custom harness (not criterion). Wall-clock + `/proc/self/` RSS/page fault metrics. 7 suites: startup, render, jump, memory, growth, rotation, concurrent. Tests mmap (plain/sequential/random/populate/phase_aware) vs pread (plain/random/sequential) backends. Output: markdown tables in `benchmark-report.md`. + +**The harness does NOT auto-generate the test file.** `main.rs` requires `/tmp/test-logviewer/extreme.log` to already exist; if missing it prints the `dd` command and exits. Generate it manually first: + +```bash +mkdir -p /tmp/test-logviewer +dd if=/dev/urandom of=/tmp/test-logviewer/extreme.log bs=1M count=5000 # ~5GB +``` + +(`crates/bench/src/data_gen.rs` has `generate_test_file` / `ensure_test_file` helpers and is used by the `growth` and `rotation` suites for per-run growable files, but it is **not** wired into `main.rs` for the primary test file.) + +Note: `log-viewer-bench` depends directly on `memmap2` / `nix` / `libc` / `memchr` — it does **not** depend on `log-viewer-core`, so it can be built standalone with `cargo build -p log-viewer-bench`.