From e158378a1390d85e2a89e3900909bdeea3360721 Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Tue, 25 Nov 2025 19:49:58 -0500 Subject: [PATCH] Command to backfill to last entry --- src/app.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ui.rs | 2 ++ 2 files changed, 95 insertions(+) diff --git a/src/app.rs b/src/app.rs index 7eb0623..1170897 100644 --- a/src/app.rs +++ b/src/app.rs @@ -493,6 +493,17 @@ impl App { self.delete_selected_frame()?; } } + KeyCode::Char('b') => { + // Backfill - adjust start time to match previous entry's end time + // Only works in ByDate mode at Entry level + if !matches!(self.log_view_selection_level, LogViewSelection::Entry) { + self.set_status_message("Backfill only works at Entry level (use h/l)"); + } else if !matches!(self.log_view_grouping, LogViewGrouping::ByDate) { + self.set_status_message("Backfill only works in By Date view (press g)"); + } else { + self.fix_entry_gap()?; + } + } KeyCode::Char('h') | KeyCode::Left => { // Zoom out selection level self.log_view_selection_level = match (&self.log_view_selection_level, &self.log_view_grouping) { @@ -824,6 +835,88 @@ impl App { Ok(()) } + fn fix_entry_gap(&mut self) -> anyhow::Result<()> { + use chrono::DateTime; + use serde_json::Value; + + self.set_status_message("Backfill function called"); + + // Check if selection is valid + if self.log_view_selected >= self.log_view_frame_indices.len() { + self.set_status_message("Invalid selection"); + return Ok(()); + } + + // Can't fix the first entry + if self.log_view_selected == 0 { + self.set_status_message("No previous entry!"); + return Ok(()); + } + + // Check if previous entry is on the same day + let current_frame_idx = self.log_view_frame_indices[self.log_view_selected]; + let prev_frame_idx = self.log_view_frame_indices[self.log_view_selected - 1]; + + if let (Some(current), Some(prev)) = ( + self.log_view_frames.get(current_frame_idx), + self.log_view_frames.get(prev_frame_idx), + ) { + // Parse timestamps + if let (Ok(curr_start), Ok(prev_stop)) = ( + DateTime::parse_from_rfc3339(¤t.start), + DateTime::parse_from_rfc3339(&prev.stop), + ) { + // Check if they're on the same day + let curr_date = curr_start.format("%Y-%m-%d").to_string(); + let prev_date = prev_stop.format("%Y-%m-%d").to_string(); + + if curr_date != prev_date { + self.set_status_message("No previous entry on same day!"); + return Ok(()); + } + + // Read watson frames file + let frames_path = dirs::config_dir() + .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))? + .join("watson") + .join("frames"); + + let frames_content = std::fs::read_to_string(&frames_path)?; + let mut frames: Value = serde_json::from_str(&frames_content)?; + + // Find and update the frame with matching id + if let Some(frames_array) = frames.as_array_mut() { + for frame in frames_array { + if let Some(frame_array) = frame.as_array_mut() { + // Frame format: [start, stop, project, id, tags, ...] + if frame_array.len() > 3 { + if let Some(id) = frame_array[3].as_str() { + if id == current.id { + // Update start timestamp (index 0) + let new_start_timestamp = prev_stop.timestamp(); + frame_array[0] = Value::Number(new_start_timestamp.into()); + break; + } + } + } + } + } + } + + // Write back to frames file + let updated_content = serde_json::to_string_pretty(&frames)?; + std::fs::write(&frames_path, updated_content)?; + + // Reload log content + self.load_log_content()?; + self.set_status_message("Entry start time adjusted"); + } + } + + self.needs_clear = true; + Ok(()) + } + fn copy_log_to_clipboard(&mut self) -> anyhow::Result<()> { if self.log_view_content.is_empty() { return Ok(()); diff --git a/src/ui.rs b/src/ui.rs index 29c4a47..65065af 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -955,6 +955,8 @@ fn render_log_view_help(frame: &mut Frame, app: &App) { "Actions:", "- e: Edit the selected entry (Entry level only)", "- x: Delete the selected entry (Entry level only)", + "- b: Backfill - set entry start time to previous entry's end time", + " (Entry level, By Date view only)", "- c: Copy selection to clipboard (works at all levels)", " Copies based on current selection level", "",