From 6096acb31225c7618b5331d0169a2fb929d8ed10 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Sun, 1 Mar 2026 01:41:37 -0500 Subject: [PATCH] journal-tail: show timestamps and extract meaningful titles Sort key normalization ensures consistent ordering across entries with different date formats (content dates vs key dates). Title extraction skips date-only lines, finds ## headers or falls back to first content line truncated at 70 chars. Also fixed: cargo bin had stale binary shadowing local bin install. --- src/main.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 67af5f2..1320aa2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1188,14 +1188,26 @@ fn cmd_journal_tail(args: &[String]) -> Result<(), String> { let date_re = regex::Regex::new(r"(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})").unwrap(); let key_date_re = regex::Regex::new(r"^journal\.md#j-(\d{4}-\d{2}-\d{2}[t-]\d{2}-\d{2})").unwrap(); - let extract_sort_key = |node: &capnp_store::Node| -> String { - // Try content header first (## 2026-02-28T23:11) - if let Some(caps) = date_re.captures(&node.content) { - return caps[1].to_string(); + let normalize_date = |s: &str| -> String { + // Normalize to YYYY-MM-DDTHH:MM for consistent sorting + let s = s.replace('t', "T"); + // Key dates use dashes everywhere: 2026-02-28-23-11 + // Content dates use dashes and colons: 2026-02-28T23:11 + // Normalize: first 10 chars keep dashes, rest convert dashes to colons + if s.len() >= 16 { + format!("{}T{}", &s[..10], s[11..].replace('-', ":")) + } else { + s } - // Try key (journal.md#j-2026-02-28t23-11-...) + }; + let extract_sort_key = |node: &capnp_store::Node| -> String { + // Try key first (journal.md#j-2026-02-28t23-11-...) if let Some(caps) = key_date_re.captures(&node.key) { - return caps[1].replace('t', "T").replace('-', ":"); + return normalize_date(&caps[1]); + } + // Try content header (## 2026-02-28T23:11) + if let Some(caps) = date_re.captures(&node.content) { + return normalize_date(&caps[1]); } // Fallback: use node timestamp format!("{:.0}", node.timestamp) @@ -1206,11 +1218,37 @@ fn cmd_journal_tail(args: &[String]) -> Result<(), String> { .collect(); journal.sort_by_key(|n| extract_sort_key(n)); - // Show last N + // Show last N — each entry: [timestamp] ## Title let skip = if journal.len() > n { journal.len() - n } else { 0 }; for node in journal.iter().skip(skip) { - println!("{}", node.content); - println!(); + let ts = extract_sort_key(node); + // Find a meaningful title: first ## header, or first non-date non-empty line + let mut title = String::new(); + for line in node.content.lines() { + let stripped = line.trim(); + if stripped.is_empty() { continue; } + // Skip date-only lines like "## 2026-03-01T01:22" + if date_re.is_match(stripped) && stripped.len() < 25 { continue; } + if stripped.starts_with("## ") { + title = stripped[3..].to_string(); + break; + } else if stripped.starts_with("# ") { + title = stripped[2..].to_string(); + break; + } else { + // Use first content line, truncated + title = if stripped.len() > 70 { + format!("{}...", &stripped[..67]) + } else { + stripped.to_string() + }; + break; + } + } + if title.is_empty() { + title = node.key.clone(); + } + println!("[{}] {}", ts, title); } Ok(())