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

12 KiB

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)

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.
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

[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.