Skip to content

Commit 37d0fb9

Browse files
foharaclaude
andcommitted
feat: Improve lash status with explicit in-progress and recently created
- Change "In Progress" section to only show tasks explicitly marked [>] instead of all open tasks - Add "Recently Created" section showing open tasks from recently modified files, appearing below "Recently Completed" - Add find_recently_created() method to TaskRepository Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 40f3e69 commit 37d0fb9

2 files changed

Lines changed: 122 additions & 4 deletions

File tree

‎crates/lash-cli/src/commands/status.rs‎

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub struct StatusOutput {
5252
pub in_progress: Vec<TaskSummary>,
5353
pub blocked: Vec<TaskSummary>,
5454
pub recently_completed: Vec<TaskSummary>,
55+
pub recently_created: Vec<TaskSummary>,
5556
pub summary: StatusSummary,
5657
}
5758

@@ -108,6 +109,7 @@ impl From<StatusCounts> for StatusSummary {
108109
/// # Returns
109110
///
110111
/// Exit code: 0 (success), 3 (DB error)
112+
#[allow(clippy::too_many_lines)]
111113
pub fn execute(args: &StatusArgs) -> Result<i32> {
112114
// Determine project root
113115
let project_root = if let Some(ref root) = args.project_root {
@@ -185,9 +187,9 @@ pub fn execute(args: &StatusArgs) -> Result<i32> {
185187
.get_status_counts()
186188
.context("Failed to query status counts")?;
187189

188-
// Get in-progress tasks
190+
// Get in-progress tasks (explicitly marked as in-progress)
189191
let in_progress = task_repo
190-
.find_by_status(TaskStatus::Open)
192+
.find_by_status(TaskStatus::InProgress)
191193
.context("Failed to query in-progress tasks")?;
192194
let in_progress: Vec<TaskRecord> = in_progress.into_iter().take(args.limit).collect();
193195

@@ -203,16 +205,34 @@ pub fn execute(args: &StatusArgs) -> Result<i32> {
203205
.find_recently_completed(since_timestamp, args.limit)
204206
.context("Failed to query recently completed tasks")?;
205207

208+
// Get recently created tasks
209+
let recently_created = task_repo
210+
.find_recently_created(since_timestamp, args.limit)
211+
.context("Failed to query recently created tasks")?;
212+
206213
// Output results
207214
if args.json {
208-
output_json(&in_progress, &blocked, &recently_completed, &counts)?;
215+
output_json(
216+
&in_progress,
217+
&blocked,
218+
&recently_completed,
219+
&recently_created,
220+
&counts,
221+
)?;
209222
} else if args.compact {
210-
output_compact(&in_progress, &blocked, &recently_completed, &counts);
223+
output_compact(
224+
&in_progress,
225+
&blocked,
226+
&recently_completed,
227+
&recently_created,
228+
&counts,
229+
);
211230
} else {
212231
output_text(
213232
&in_progress,
214233
&blocked,
215234
&recently_completed,
235+
&recently_created,
216236
&counts,
217237
&args.since,
218238
theme.as_ref(),
@@ -275,6 +295,7 @@ fn output_json(
275295
in_progress: &[TaskRecord],
276296
blocked: &[TaskRecord],
277297
recently_completed: &[TaskRecord],
298+
recently_created: &[TaskRecord],
278299
counts: &StatusCounts,
279300
) -> Result<()> {
280301
let output = StatusOutput {
@@ -284,6 +305,7 @@ fn output_json(
284305
.iter()
285306
.map(|t| t.clone().into())
286307
.collect(),
308+
recently_created: recently_created.iter().map(|t| t.clone().into()).collect(),
287309
summary: counts.clone().into(),
288310
};
289311

@@ -296,11 +318,13 @@ fn output_compact(
296318
in_progress: &[TaskRecord],
297319
blocked: &[TaskRecord],
298320
recently_completed: &[TaskRecord],
321+
recently_created: &[TaskRecord],
299322
counts: &StatusCounts,
300323
) {
301324
println!("in_progress: {}", in_progress.len());
302325
println!("blocked: {}", blocked.len());
303326
println!("recent_done: {}", recently_completed.len());
327+
println!("recent_created: {}", recently_created.len());
304328
println!(
305329
"total: {} open: {} done: {} waived: {} blocked: {}",
306330
counts.total, counts.open, counts.done, counts.waived, counts.blocked
@@ -313,6 +337,7 @@ fn output_text(
313337
in_progress: &[TaskRecord],
314338
blocked: &[TaskRecord],
315339
recently_completed: &[TaskRecord],
340+
recently_created: &[TaskRecord],
316341
counts: &StatusCounts,
317342
since: &str,
318343
theme: Option<&CliTheme>,
@@ -376,6 +401,27 @@ fn output_text(
376401
println!();
377402
}
378403

404+
// Recently Created section
405+
if !recently_created.is_empty() {
406+
if let Some(t) = theme {
407+
println!(
408+
"{} ({} in last {since})",
409+
t.style_info("Recently Created"),
410+
recently_created.len()
411+
);
412+
} else {
413+
println!(
414+
"Recently Created ({} in last {since})",
415+
recently_created.len()
416+
);
417+
}
418+
419+
for task in recently_created {
420+
print_task_summary(task, theme);
421+
}
422+
println!();
423+
}
424+
379425
// Summary section
380426
#[allow(
381427
clippy::cast_precision_loss,

‎crates/lash-db/src/repository/tasks.rs‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,46 @@ impl<'conn> TaskRepository<'conn> {
702702
Ok(tasks)
703703
}
704704

705+
/// Find recently created tasks
706+
///
707+
/// Finds tasks with status 'open' in files modified since the given timestamp.
708+
/// This serves as a proxy for recently created tasks since task creation
709+
/// updates the file's modification time.
710+
/// Results are ordered by file modification time (most recent first).
711+
///
712+
/// # Arguments
713+
///
714+
/// * `since_timestamp` - Unix timestamp (seconds since epoch)
715+
/// * `limit` - Maximum number of tasks to return
716+
///
717+
/// # Errors
718+
///
719+
/// Returns error if query fails
720+
pub fn find_recently_created(
721+
&self,
722+
since_timestamp: i64,
723+
limit: usize,
724+
) -> DbResult<Vec<TaskRecord>> {
725+
#[allow(clippy::cast_possible_wrap)]
726+
let limit_i64 = limit as i64;
727+
728+
let mut stmt = self.conn.prepare(
729+
"SELECT t.id, t.file_id, t.local_id, t.full_id, t.title, t.status, t.depth, t.parent_id, t.order_index, t.owner, t.estimate, t.body, t.metadata, t.contextual_notes
730+
FROM tasks t
731+
JOIN files f ON t.file_id = f.id
732+
WHERE t.status = 'open'
733+
AND f.mtime >= ?1
734+
ORDER BY f.mtime DESC
735+
LIMIT ?2",
736+
)?;
737+
738+
let tasks = stmt
739+
.query_map([since_timestamp, limit_i64], Self::row_to_task_record)?
740+
.collect::<Result<Vec<_>, _>>()?;
741+
742+
Ok(tasks)
743+
}
744+
705745
/// Helper to convert a row to `TaskRecord`
706746
fn row_to_task_record(row: &rusqlite::Row) -> rusqlite::Result<TaskRecord> {
707747
let metadata_json: String = row.get(12)?;
@@ -2008,4 +2048,36 @@ mod tests {
20082048
assert_eq!(ids[1], "test#mike");
20092049
assert_eq!(ids[2], "test#zulu");
20102050
}
2051+
2052+
#[test]
2053+
fn test_find_recently_created() {
2054+
let temp_db = NamedTempFile::new().unwrap();
2055+
let conn = init_database(temp_db.path()).unwrap();
2056+
2057+
let file = create_test_file("test.md", "test");
2058+
let file_repo = FileRepository::new(&conn);
2059+
let file_db_id = file_repo.insert(&file).unwrap();
2060+
2061+
let task_repo = TaskRepository::new(&conn);
2062+
2063+
// Create open tasks
2064+
let task1 = create_test_task("task1", "Open Task 1", 0, None, 0);
2065+
task_repo.insert(&task1, file_db_id, "test").unwrap();
2066+
2067+
let task2 = create_test_task("task2", "Open Task 2", 0, None, 1);
2068+
task_repo.insert(&task2, file_db_id, "test").unwrap();
2069+
2070+
// Create a done task
2071+
let mut task3 = create_test_task("task3", "Done Task", 0, None, 2);
2072+
task3.status = TaskStatus::Done;
2073+
task_repo.insert(&task3, file_db_id, "test").unwrap();
2074+
2075+
// Find recently created (should only return open tasks)
2076+
// Using timestamp 0 to get all tasks
2077+
let recently_created = task_repo.find_recently_created(0, 10).unwrap();
2078+
assert_eq!(recently_created.len(), 2);
2079+
assert!(recently_created
2080+
.iter()
2081+
.all(|t| t.status == TaskStatus::Open));
2082+
}
20112083
}

0 commit comments

Comments
 (0)