Files
bbs-md/.planning/research/STACK.md
T

164 lines
12 KiB
Markdown

# Technology Stack
**Project:** bbs-md — Rust TUI Markdown Vault Reader / BBS System
**Researched:** 2026-02-28
**Confidence Note:** Web and docs access was unavailable during this research session. Versions are derived from: (1) the project's existing Cargo.lock for already-pinned deps, and (2) training knowledge for new additions. All new library versions are flagged with their confidence level and must be verified against crates.io before pinning.
---
## Existing Stack (Confirmed from Cargo.lock)
These are locked and do not need verification — they are already in the dependency tree.
| Library | Locked Version | Role |
|---------|---------------|------|
| ratatui | 0.30.0 | TUI framework — keep as-is |
| ratatui-core | 0.1.0 | Core types (transitive) |
| ratatui-crossterm | 0.1.0 | Crossterm backend (transitive) |
| ratatui-macros | 0.7.0 | Layout macros (transitive) |
| ratatui-widgets | 0.3.0 | Built-in widgets (transitive) |
| crossterm | 0.29.0 | Terminal I/O (transitive via ratatui) |
| anyhow | 1.0.102 | Error handling — keep |
| thiserror | 2.0.18 | Structured errors (transitive, available) |
| termwiz | 0.23.3 | Terminal capabilities (transitive) |
| regex | 1.12.3 | Regex (transitive, available to use) |
| unicode-width | 0.2.2 | Display width (transitive) |
| serde | 1.0.228 | Serialization (transitive, available) |
| serde_json | 1.0.149 | JSON (transitive, available) |
---
## Recommended Stack — New Additions
### Core Framework (already decided)
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| ratatui | 0.30.0 (existing) | TUI rendering and layout | Already scaffolded; ratatui is the dominant Rust TUI framework. 0.30.x is current stable. The split into ratatui-core/ratatui-widgets is the new architecture introduced in 0.29+. Do not downgrade. |
| crossterm | 0.29.0 (existing) | Terminal backend | Ships with ratatui. Provides raw mode, cursor, event loop. Do not add a second backend. |
### Markdown Parsing
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| pulldown-cmark | `^0.12` | Parse CommonMark markdown into events | **Use pulldown-cmark, not comrak.** pulldown-cmark is event-based (streaming, zero-copy), not a full AST allocator. For a TUI renderer that walks nodes once to produce styled spans, the event model maps perfectly to ratatui's `Line`/`Span` construction. comrak builds a full AST tree, consumes more memory, and adds GFM extension dependencies you do not need. pulldown-cmark supports tables and footnotes via feature flags. Confidence: MEDIUM — version 0.12 was the 2024/2025 major; verify against crates.io. |
**Why not tui-markdown or similar?** Pre-built markdown-to-ratatui renderers exist (e.g., `ratatui-markdown`, `tui-markdown`) but they impose their own styling decisions and fight the retro BBS aesthetic you need. Writing the pulldown-cmark → ratatui spans bridge yourself gives complete control over box-drawing, color palettes, and header decoration. Total code for a basic bridge is ~300 lines. Worth it.
### Filesystem Watching
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| notify | `^8` | Watch vault directory for .md file changes | **Use notify, not inotify directly.** notify is the de-facto cross-platform file watching crate in Rust — uses kqueue on macOS, inotify on Linux, FSEvents on macOS (optional), ReadDirectoryChangesW on Windows. For a Linux SSH server deployment, this is fine. The `notify::recommended_watcher` function picks the best backend automatically. Version 8.x introduced the `EventKind` redesign. Confidence: MEDIUM — notify 8.x is the current major; verify on crates.io. |
| notify-debouncer-full | `^0.4` | Debounce rapid file change events | Markdown editors (Obsidian, vim) fire multiple events per save (write + metadata update). Without debouncing you rebuild the file index on every partial write. `notify-debouncer-full` batches events within a configurable window (200ms is good). Ships alongside `notify` in the same repo. Confidence: MEDIUM — same version caveat as notify. |
**Threading strategy with notify:** Run the watcher in a dedicated `std::thread`, send `notify::Event` through a `std::sync::mpsc::channel` into the main event loop. This avoids tokio entirely — the ratatui event loop already has a crossterm event poller, so you integrate watcher events into the same `crossterm::event::poll` loop with a `mpsc::try_recv()` check each frame. Do not add tokio just for filesystem watching.
### Async Runtime
| Decision | Verdict |
|----------|---------|
| tokio | **Do not add tokio.** There is no async I/O here. Each SSH session is its own OS process. The ratatui event loop is synchronous. File watching runs in a thread. Adding tokio buys nothing and adds ~2MB binary size and compilation complexity. If a future feature needs async (e.g., fetching remote content), add tokio then. |
### Configuration
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| toml | `^0.8` | Parse `bbs.toml` vault configuration file | The vault should support a config file for: vault root path, color theme, index.md filename, disabled link types, etc. TOML is the Rust-idiomatic config format (Cargo uses it). `toml` crate pairs with serde (already in dep tree) via `#[derive(Deserialize)]`. Confidence: HIGH — toml 0.8 is established stable. |
### Wiki-Link Parsing
| Decision | Verdict |
|----------|---------|
| regex | **Use the existing `regex` crate already in the dep tree.** Wiki-links (`[[Page Name]]`) are not CommonMark — pulldown-cmark will not parse them. A pre-pass over raw markdown text with a regex (`\[\[([^\]]+)\]\]`) before handing off to pulldown-cmark is the correct approach. Replace `[[Page Name]]` with a standard link `[Page Name](Page_Name.md)` before parsing, OR parse pulldown-cmark events and intercept Code events that contain `[[`. The pre-pass substitution approach is simpler. No new dependency needed. |
### Logging
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| tracing | `^0.1` | Structured logging | **Critical requirement:** a TUI app cannot write to stderr/stdout — it corrupts the terminal. `tracing` with `tracing-appender` writes logs to a rotating file. This is non-optional for any debugging after the "Hello world" stage. Confidence: HIGH — tracing 0.1 is the Rust async/structured logging standard. |
| tracing-subscriber | `^0.3` | Configure tracing output | Pairs with tracing. Use `EnvFilter` so `RUST_LOG=debug` controls log level. Confidence: HIGH. |
| tracing-appender | `^0.2` | Non-blocking file log sink | Ships with the tracing ecosystem. Writes to `/tmp/bbs-md.log` or a configured path without blocking the render loop. Confidence: HIGH. |
### State Management
| Decision | Verdict |
|----------|---------|
| No external state library | Ratatui's model is: immutable `App` struct → render → event → mutate `App` → repeat. This is sufficient. Use a single `App` struct holding `current_doc: Arc<Document>`, `history: Vec<PathBuf>`, `scroll: usize`. No Redux/signals/observable libraries needed. |
### Error Handling
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| anyhow | 1.0.102 (existing) | Application-level errors | Already present. Use `anyhow::Result` at the top level. For the main event loop, handle errors by displaying them as a TUI overlay rather than crashing — SSH users get a helpful error panel instead of a dead process. |
| thiserror | 2.0.18 (existing) | Domain error types | Use for `VaultError`, `ParseError`, `NavigationError`. These convert into anyhow automatically. |
---
## Alternatives Considered
| Category | Recommended | Alternative | Why Not |
|----------|-------------|-------------|---------|
| Markdown parser | pulldown-cmark | comrak | comrak builds a full AST in memory; heavier allocation model for what is essentially a streaming render task. pulldown-cmark is faster for single-pass rendering. |
| Markdown parser | pulldown-cmark | markdown-it (JS via WASM) | Absurd. This is a Rust project. |
| Markdown parser | pulldown-cmark | tui-markdown / ratatui-markdown | Canned styling; incompatible with retro BBS aesthetic requirements. |
| File watching | notify | inotify (Linux-only) | notify wraps inotify and adds macOS/Windows fallbacks. Since vault might be developed on macOS and deployed to Linux, cross-platform matters during development. |
| File watching | notify | fs-watch | Unmaintained as of 2024. |
| Config format | toml | serde_json | JSON has no comments. Vault authors need to annotate config. TOML is the Rust community default. |
| Config format | toml | ron | RON is Rust-specific; vault authors are likely from the Obsidian/markdown world, not Rust devs. |
| Async runtime | (none) | tokio | Zero async I/O in the hot path. Single-process per SSH session. tokio adds compile time, binary size, and cognitive overhead for no benefit. |
| Logging | tracing | env_logger | env_logger writes to stderr which corrupts TUI rendering. tracing-appender writes to a file. |
| Logging | tracing | println! / eprintln! | Writes to stderr/stdout — immediate TUI corruption. |
| SSH server | (none — OS daemon) | russh / thrussh | Out of scope per project decisions. The binary is a login shell, not an SSH server. Do not add embedded SSH. |
---
## Installation
```toml
[dependencies]
ratatui = "0.30.0" # existing — do not change
anyhow = "1.0" # existing — do not change
pulldown-cmark = { version = "0.12", default-features = false, features = ["tables", "footnotes"] }
notify = "8"
notify-debouncer-full = "0.4"
toml = "0.8"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
```
**Version pinning note:** All versions above for new additions (pulldown-cmark, notify, notify-debouncer-full, toml, tracing*) should be verified against crates.io before committing. The train cutoff for this research is August 2025; patch versions may have advanced. Use `cargo add [crate]` to get the latest compatible version recommended by Cargo.
---
## Sources
| Claim | Confidence | Basis |
|-------|-----------|-------|
| ratatui 0.30.0 with sub-crate architecture | HIGH | Cargo.lock direct observation |
| crossterm 0.29.0 as backend | HIGH | Cargo.lock direct observation |
| anyhow 1.0.102, thiserror 2.0.18 | HIGH | Cargo.lock direct observation |
| pulldown-cmark is event-based, preferred for rendering | HIGH | Training knowledge — well-established in Rust ecosystem |
| pulldown-cmark ~0.12 as current major | MEDIUM | Training knowledge — verify on crates.io |
| notify 8.x as current major, kqueue/inotify backends | MEDIUM | Training knowledge — verify on crates.io |
| notify-debouncer-full 0.4 | MEDIUM | Training knowledge — verify on crates.io |
| toml 0.8 stable | HIGH | Training knowledge — used by Cargo itself |
| tracing/tracing-subscriber/tracing-appender 0.1/0.3/0.2 | HIGH | Training knowledge — standard Rust logging ecosystem |
| tokio not needed | HIGH | Architectural analysis — no async I/O in hot path |
| Wiki-link regex pre-pass pattern | HIGH | Architectural analysis — CommonMark spec does not include wiki-links |
---
## Key Constraints for Downstream Phases
1. **No tokio.** Thread-based concurrency with `std::sync::mpsc` only. This keeps the event loop simple and avoids async executor confusion.
2. **ratatui 0.30 API uses the new sub-crate structure.** Imports come from `ratatui::prelude::*` and `ratatui::widgets::*`. The split into ratatui-core/ratatui-widgets happened in 0.29 — do not follow pre-0.29 tutorials.
3. **Rust edition 2024 is valid.** The CONCERNS.md flags this as invalid, but Rust 1.85 (February 2025) stabilized edition 2024. This is not a bug. No change needed.
4. **Log to file, never to stderr.** Any `eprintln!`, `println!`, or logger that writes to a terminal fd will corrupt the ratatui-rendered output in the connected SSH session.
5. **pulldown-cmark features must be explicit.** With `default-features = false`, you opt into only what you need. Tables and footnotes are the relevant extras for an Obsidian-style vault.