Files
bbs-md/.planning/phases/02-vault-core-and-rendering/02-01-PLAN.md
T
2026-02-28 22:02:14 +01:00

157 lines
7.2 KiB
Markdown

---
phase: 02-vault-core-and-rendering
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- Cargo.toml
- src/vault.rs
- src/highlighter.rs
autonomous: true
requirements:
- REND-03
- REND-10
- NAV-06
- NAV-07
must_haves:
truths:
- "pulldown-cmark, syntect, and syntect-tui compile without errors alongside existing ratatui 0.30"
- "vault.rs can load a markdown file from a vault path and return structured success/missing/error states"
- "highlighter.rs initializes syntect once and can highlight a code string into Vec<Line<'static>> with CGA 16-color palette"
artifacts:
- path: "Cargo.toml"
provides: "pulldown-cmark, syntect, syntect-tui dependencies"
contains: "pulldown-cmark"
- path: "src/vault.rs"
provides: "VaultDocument enum and load_document function"
contains: "VaultDocument"
- path: "src/highlighter.rs"
provides: "syntect initialization, CGA color mapping, highlight_code function"
contains: "syntect_color_to_cga"
key_links:
- from: "src/highlighter.rs"
to: "syntect"
via: "OnceLock<SyntaxSet> and OnceLock<ThemeSet> initialized once"
pattern: "OnceLock.*SyntaxSet"
- from: "src/vault.rs"
to: "std::fs"
via: "read_to_string for markdown file loading"
pattern: "read_to_string"
---
<objective>
Add Phase 2 dependencies (pulldown-cmark, syntect, syntect-tui) to Cargo.toml, create the vault file-loading module, and create the syntax highlighter module with CGA 16-color mapping.
Purpose: Establish the foundation libraries and I/O layer that the markdown renderer (Plan 02) and app integration (Plan 03) build upon.
Output: Three files modified/created; `cargo build` succeeds with all new dependencies.
</objective>
<execution_context>
@/Users/ruohki/.claude/get-shit-done/workflows/execute-plan.md
@/Users/ruohki/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-vault-core-and-rendering/02-RESEARCH.md
@.planning/phases/02-vault-core-and-rendering/02-CONTEXT.md
@src/config.rs
@Cargo.toml
</context>
<tasks>
<task type="auto">
<name>Task 1: Add dependencies and create vault file-loading module</name>
<files>Cargo.toml, src/vault.rs</files>
<action>
**Cargo.toml:** Add three new dependencies alongside the existing ones:
```toml
pulldown-cmark = "0.13.1"
syntect = { version = "5.3", default-features = false, features = ["default-fancy"] }
syntect-tui = "3.0"
```
Note: `default-features = false, features = ["default-fancy"]` avoids the Oniguruma C library — pure Rust regex engine for clean SSH server builds.
**src/vault.rs:** Create a new module with:
1. `VaultDocument` enum with three variants:
- `Loaded { path: PathBuf, content: String }` — file read successfully
- `Missing { path: PathBuf }` — file does not exist (io::ErrorKind::NotFound)
- `ReadError { path: PathBuf, reason: String }` — other I/O error
2. `load_document(vault_path: &Path, relative: &str) -> VaultDocument`:
- Join `vault_path` with `relative` to get the full path
- Use `std::fs::read_to_string()` with proper match on the Result
- Map `ErrorKind::NotFound` to `VaultDocument::Missing`
- Map other errors to `VaultDocument::ReadError`
- Return `VaultDocument::Loaded` on success
The function takes `vault_path` (from Config) and a relative filename (e.g., "index.md").
Do NOT register the module in main.rs yet — Plan 03 handles wiring.
</action>
<verify>`cargo check` passes with no errors (new deps resolve, vault.rs compiles). Run `cargo check 2>&1` and confirm exit code 0.</verify>
<done>Cargo.toml has pulldown-cmark 0.13.1, syntect 5.3 with default-fancy, and syntect-tui 3.0. vault.rs exists with VaultDocument enum and load_document function. `cargo check` succeeds.</done>
</task>
<task type="auto">
<name>Task 2: Create syntax highlighter module with CGA color mapping</name>
<files>src/highlighter.rs</files>
<action>
Create `src/highlighter.rs` with these components:
1. **Module-level OnceLock statics** for one-time initialization:
```rust
static SYNTAX_SET: OnceLock<SyntaxSet> = OnceLock::new();
static THEME_SET: OnceLock<ThemeSet> = OnceLock::new();
```
2. **`init_highlighter()`** — call once from main.rs before App::new():
- `SYNTAX_SET.get_or_init(SyntaxSet::load_defaults_newlines);`
- `THEME_SET.get_or_init(ThemeSet::load_defaults);`
3. **`syntax_set() -> &'static SyntaxSet`** and **`theme_set() -> &'static ThemeSet`** accessor functions that call `.get().expect(...)`.
4. **`syntect_color_to_cga(c: syntect::highlighting::Color) -> ratatui::style::Color`** — RGB-to-CGA Euclidean distance mapper using the 16 CGA palette entries exactly as documented in the research (Pattern 8). The CGA palette entries:
- Black(0,0,0), Red(170,0,0), Green(0,170,0), Yellow(170,170,0), Blue(0,0,170), Magenta(170,0,170), Cyan(0,170,170), Gray(170,170,170)
- DarkGray(85,85,85), LightRed(255,85,85), LightGreen(85,255,85), LightYellow(255,255,85), LightBlue(85,85,255), LightMagenta(255,85,255), LightCyan(85,255,255), White(255,255,255)
5. **`highlight_code(code: &str, lang: &str) -> Vec<Line<'static>>`** — the public API for code block highlighting:
- Look up syntax by token first, then by name, fall back to plain text
- Use `HighlightLines::new(syntax, &theme_set().themes["base16-ocean.dark"])`
- For each line in `code.lines()`: call `highlight_line()`, map each `(Style, &str)` tuple to a `Span::styled(text.to_string(), Style::default().fg(syntect_color_to_cga(style.foreground)))`, collect into `Line::from(spans)`
- Return `Vec<Line<'static>>` (all owned strings, no lifetime leakage)
Do NOT register the module in main.rs yet — Plan 03 handles wiring. But do ensure the file compiles in isolation (no unresolved imports that depend on other new modules).
</action>
<verify>`cargo check` passes. Verify highlighter.rs compiles by temporarily adding `mod highlighter;` to main.rs, running `cargo check`, then removing it. Or simply verify the file has no syntax errors by checking `cargo check` output.</verify>
<done>highlighter.rs exists with init_highlighter(), syntax_set(), theme_set(), syntect_color_to_cga(), and highlight_code() functions. All use OnceLock for one-time init. Output is Vec<Line<'static>> with CGA 16-color palette.</done>
</task>
</tasks>
<verification>
1. `cargo build` succeeds with all new dependencies
2. `src/vault.rs` contains `VaultDocument` enum with `Loaded`, `Missing`, `ReadError` variants
3. `src/vault.rs` contains `load_document()` function
4. `src/highlighter.rs` contains `init_highlighter()`, `highlight_code()`, `syntect_color_to_cga()`
5. No changes to existing Phase 1 files (app.rs, main.rs, config.rs, signals.rs, terminal.rs) other than Cargo.toml
</verification>
<success_criteria>
- `cargo build` compiles cleanly with pulldown-cmark 0.13.1, syntect 5.3, and syntect-tui 3.0
- vault.rs provides file loading with structured error states (Loaded/Missing/ReadError)
- highlighter.rs provides one-shot syntect initialization and CGA-mapped syntax highlighting
- Both new modules produce `'static` lifetime outputs (no borrowed data leaking)
</success_criteria>
<output>
After completion, create `.planning/phases/02-vault-core-and-rendering/02-01-SUMMARY.md`
</output>