9.8 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-bbs-polish-and-live-content | 01 | execute | 1 |
|
true |
|
|
Purpose: The splash screen makes the landing page feel like a real BBS with colorful ANSI art. The file metadata (last-modified timestamp and file size) in the status bar gives users context about content freshness.
Output: src/splash.rs module, updated src/app.rs with PageMeta and splash prepend, updated Cargo.toml with all Phase 4 dependencies (ansi-to-tui, notify, walkdir — added now so later plans don't need to touch Cargo.toml).
<execution_context> @/Users/ruohki/.claude/get-shit-done/workflows/execute-plan.md @/Users/ruohki/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-bbs-polish-and-live-content/04-RESEARCH.md @src/app.rs @src/main.rs @Cargo.toml Task 1: Add Phase 4 dependencies and create splash + metadata modules Cargo.toml, src/splash.rs, src/main.rs 1. Add all three Phase 4 dependencies to Cargo.toml: ```toml notify = "6.1" ansi-to-tui = "8.0" walkdir = "2.5" ```-
Run
cargo tree | grep ratatui-coreto verify ansi-to-tui resolves to a single ratatui-core version. If two versions appear, addratatui-core = "0.1"to force unification. -
Create
src/splash.rswith:use std::path::Path; use ansi_to_tui::IntoText; use ratatui::text::Line; /// Load ANSI art from `splash.txt` in the vault root. /// Returns None if file doesn't exist or can't be parsed (graceful degradation). pub fn load_splash(vault_path: &Path) -> Option<Vec<Line<'static>>> { let bytes: Vec<u8> = std::fs::read(vault_path.join("splash.txt")).ok()?; let text = bytes.into_text().ok()?; Some(text.lines) } -
Add
mod splash;tosrc/main.rsmodule declarations (aftermod signals;). -
Run
cargo buildto verify compilation with new dependencies.cargo buildsucceeds.cargo tree | grep ratatui-coreshows exactly one version.src/splash.rsexists. All three Phase 4 crate dependencies compile. splash.rs provides load_splash(). Module is declared in main.rs.
use std::time::UNIX_EPOCH;
/// File metadata for status bar display.
struct PageMeta {
modified: String, // "Feb 25, 2026"
size: String, // "2.4 KB"
}
fn read_page_meta(full_path: &Path) -> Option<PageMeta> {
let meta = std::fs::metadata(full_path).ok()?;
let secs = meta.modified().ok()?.duration_since(UNIX_EPOCH).ok()?.as_secs();
let (y, m, d) = unix_secs_to_ymd(secs);
let months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
Some(PageMeta {
modified: format!("{} {}, {}", months[(m-1) as usize], d, y),
size: format_file_size(meta.len()),
})
}
fn unix_secs_to_ymd(secs: u64) -> (u32, u32, u32) {
let mut days = (secs / 86400) as u32;
let mut year = 1970u32;
loop {
let days_in_year = if is_leap(year) { 366 } else { 365 };
if days < days_in_year { break; }
days -= days_in_year;
year += 1;
}
let leap = is_leap(year);
let month_days: [u32; 12] = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let mut month = 0u32;
for (i, &d) in month_days.iter().enumerate() {
if days < d { month = i as u32 + 1; break; }
days -= d;
}
(year, month, days + 1)
}
fn is_leap(year: u32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
fn format_file_size(bytes: u64) -> String {
match bytes {
0..=1023 => format!("{} B", bytes),
1024..=1_048_575 => format!("{:.1} KB", bytes as f64 / 1024.0),
_ => format!("{:.1} MB", bytes as f64 / 1_048_576.0),
}
}
-
Add
page_meta: Option<PageMeta>field to theAppstruct (Phase 4 additions section). Initialize toNoneinApp::new(). -
Update
navigate_to()— after loading a document successfully (inside theVaultDocument::Loadedmatch arm):- Compute full path:
let full_path = vault_path.join(vault_relative); - Read metadata:
self.page_meta = read_page_meta(&full_path); - Check if this is the index page and prepend splash if so:
if vault_relative == "index.md" { if let Some(mut splash_lines) = crate::splash::load_splash(&vault_path) { splash_lines.push(Line::default()); // blank separator splash_lines.extend(lines); lines = splash_lines; // Adjust link records: offset all line_index values by the splash line count // splash_count = splash_lines_original_len + 1 (for blank separator) } } - IMPORTANT: When splash lines are prepended, all
link_recordsline_index values must be offset by the number of splash lines added. Store the splash line count and add it to each record'sline_index. - For Missing/ReadError arms: set
self.page_meta = None;
- Compute full path:
-
Apply the same splash prepend logic in
navigate_back()andnavigate_forward()— when the target path is "index.md", prepend splash lines and adjust link_record line indices. Also set page_meta viaread_page_meta(). -
Apply splash + metadata in
main.rsinitial load too — after rendering index.md, prepend splash lines if available and adjust link_records, then compute initial page_meta. -
Update
handle_resize(): when re-rendering, ifcurrent_path == "index.md", prepend splash lines again and adjust link_records. Also note: splash lines are NOT re-rendered through render_markdown — they come fromload_splash()directly and are prepended after the markdown render. -
Update
draw_status_bar()to include metadata on the right side:- In the normal (non-quit-prompt) branch, before the keyboard hints section:
- If
self.page_metaisSome(meta):- Insert
format!("Last modified: {} | {}", meta.modified, meta.size)intoright_parts(before the hints entry).
- Insert
- Implement graceful truncation: if
left.len() + right.len() >= width, progressively drop metadata fields:- First drop file size (show only "Last modified: ...").
- Then drop the entire metadata line.
- Then abbreviate keyboard hints if still too wide.
- Use
width.saturating_sub()to prevent underflow.
-
Run
cargo buildto verify.cargo buildsucceeds. Status bar shows metadata when a document is loaded (visible on next run with a vault). Splash prepend logic handles both presence and absence of splash.txt. index.md shows splash art header when splash.txt exists. All loaded pages show "Last modified: Mon DD, YYYY | X.X KB" in status bar. Status bar gracefully truncates on narrow terminals.
<success_criteria>
- splash.txt ANSI art renders above index.md content with correct colors via ansi-to-tui
- Missing splash.txt causes no error — graceful degradation to normal index.md
- Every loaded document page shows BBS-format timestamp and file size in status bar
- Status bar layout: left=breadcrumb, right=metadata+hints, with graceful truncation
- Link Tab-cycling still works correctly on index.md when splash lines are prepended (line_index offsets are correct) </success_criteria>