From 8c552ac3a6cdf34e0daaf8cacf2b370aacb89322 Mon Sep 17 00:00:00 2001 From: Huijing Huang Date: Wed, 10 Sep 2025 23:01:30 -0700 Subject: [PATCH 1/2] add list subcommand for devlog --- src/cli.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/storage.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index a11469d..c7a8015 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -37,6 +37,8 @@ pub enum Commands { #[arg(value_name = "YYYYMMDD")] id: String, }, + /// List all entries + List, } impl Cli { @@ -57,6 +59,9 @@ impl Cli { Commands::Show { id } => { Self::handle_show_command(id, &storage)?; } + Commands::List => { + Self::handle_list_command(&storage)?; + } } Ok(()) @@ -160,6 +165,49 @@ impl Cli { Ok(()) } + /// Handle the list subcommand + fn handle_list_command(storage: &EntryStorage) -> Result<(), Box> { + let entry_ids = storage.list_entry_ids()?; + + if entry_ids.is_empty() { + println!("No entries found. Create one with 'devlog new'"); + return Ok(()); + } + + println!(); + println!("DevLog Entries"); + println!("══════════════"); + + for entry_id in &entry_ids { + // Load the entry to get its content + if let Some(entry) = Entry::load(entry_id, storage)? { + let state = entry.current_state(); + + // Get the first line of content, truncated to ~60 characters + let first_line = state.content.lines().next().unwrap_or("(empty)").trim(); + + let display_content = if first_line.len() > 60 { + format!("{}...", &first_line[..57]) + } else if state.content.lines().count() > 1 { + format!("{}...", first_line) + } else { + first_line.to_string() + }; + + println!(" {} {}", entry_id, display_content); + } + } + + println!("══════════════"); + println!("Total: {} entries", entry_ids.len()); + println!(); + println!("Commands:"); + println!(" devlog edit --id YYYYMMDD Edit an entry"); + println!(" devlog new Create a new entry"); + + Ok(()) + } + /// Display entry in default human-readable format fn display_default_format(entry: &Entry) { let state = entry.current_state(); diff --git a/src/storage.rs b/src/storage.rs index 3b36ef1..f750b0d 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -34,6 +34,41 @@ impl EntryStorage { Ok(Self { base_dir }) } + /// List all entry IDs sorted in descending order (newest first) + pub fn list_entry_ids(&self) -> Result, Box> { + let entries_dir = self.base_dir.join("entries"); + + if !entries_dir.exists() { + return Ok(Vec::new()); + } + + let mut entry_ids = Vec::new(); + + for entry in fs::read_dir(entries_dir)? { + // Entry is Result, not DirEntry + // Each individual file/directory read operation could fail due to permission, corrupted filesystem, etc. + let entry = entry?; + let path = entry.path(); + + // Only process .md files + if path.is_file() && path.extension().map_or(false, |ext| ext == "md") { + // file_stem() returns the filename without its extension + // this method can return None for paths like `/` or `..` + // `to_str()` can return None if the fielname contains invalid UTF-8 characters + if let Some(file_stem) = path.file_stem() { + if let Some(entry_id) = file_stem.to_str() { + entry_ids.push(entry_id.to_string()); + } + } + } + } + + // Sort in descending order + entry_ids.sort_by(|a, b| b.cmp(a)); + + Ok(entry_ids) + } + /// Get the event file path for a given date fn events_path(&self, date: &str) -> PathBuf { self.base_dir.join("events").join(format!("{}.jsonl", date)) From b3074d93e1a2c8f927e95ddccda2d32c1db4bcc2 Mon Sep 17 00:00:00 2001 From: Huijing Huang Date: Wed, 10 Sep 2025 23:05:11 -0700 Subject: [PATCH 2/2] Add functional requirement for TUI --- docs/tui-functional-requirements.md | 77 +++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/tui-functional-requirements.md diff --git a/docs/tui-functional-requirements.md b/docs/tui-functional-requirements.md new file mode 100644 index 0000000..273c0b2 --- /dev/null +++ b/docs/tui-functional-requirements.md @@ -0,0 +1,77 @@ +# Terminal UI (TUI) Functional Requirements - MVP + +## Overview + +This document outlines the MVP functional requirements for the interactive terminal user interface for the `devlog` command-line tool. The TUI will be activated when users run `devlog list`, providing an interactive browsing experience for journal entries. + +--- + +## Core Functional Requirements + +### 1. Navigation + +- **Arrow Keys** (`↑`, `↓`, `←`, `→`) - Navigate between entries and pages +- **Vim Keys** (`h`, `j`, `k`, `l`) - Alternative navigation (left, down, up, right) + +### 2. Entry Actions + +- **Enter** - View full content of selected entry +- **e** - Edit selected entry in $EDITOR +- **n** - Create new entry +- **q** - Quit application + +### 3. Search and Help + +- **/** or **s** - Search/filter entries (not-implemented placeholder for now) +- **h** or **?** - Show help with all keybindings + +### 4. Pagination Support + +- **Page Up/Down** or **Left/Right arrows** - Navigate between pages when there are many entries +- **Status bar** - Show current page and total entries (e.g., "Page 2/5 - Entry 15/47") + +### 5. Entry Information Display + +- **Entry list** - Show entry ID (date), first line of content, and basic metadata +- **Selection highlight** - Clear visual indication of currently selected entry + +### 6. Error Handling + +- **Confirmation prompts** - For destructive operations +- **Error messages** - Clear feedback when operations fail +- **Graceful fallback** - If TUI fails, fall back to simple list output + +--- + +## Technical Implementation Notes + +### TUI Framework + +- Use `ratatui` (modern Rust TUI framework) with `crossterm` for terminal handling + +### Entry Display Format + +``` +┌─ DevLog Entries ───────────────────────────────────────────┐ +│ > 20240910 Fixed pagination bug in user service │ +│ 20240909 Team meeting notes - Q4 planning │ +│ 20240908 Implemented user authentication flow │ +│ 20240907 Debugging database connection issues │ +└─ Page 1/3 - Entry 1/25 ──────── Press 'h' for help ────────┘ +``` + +### Help Display + +``` +┌─ Help ─────────────────────────────────────────────────────┐ +│ Navigation: │ +│ ↑/k - Up ↓/j - Down ←/h - Left →/l - Right │ +│ Page Up/Down - Navigate pages │ +│ │ +│ Actions: │ +│ Enter - View entry e - Edit n - New q - Quit │ +│ / or s - Search (coming soon) h/? - Help │ +│ │ +│ Press any key to continue... │ +└────────────────────────────────────────────────────────────┘ +```