From 4162e78b3b9d04a93f902cb9d6b181a0db35afbc Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 18 Mar 2026 20:18:18 -0600 Subject: [PATCH] Sort project cards on all projects page by activity tier --- src/cmd/webui/handlers.rs | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/cmd/webui/handlers.rs b/src/cmd/webui/handlers.rs index 2bc135f2cbf6330585813da5fb0d051c20d0fc7f..b5606c26385b017c73d1c8f61d244d80f44d43ff 100644 --- a/src/cmd/webui/handlers.rs +++ b/src/cmd/webui/handlers.rs @@ -20,6 +20,25 @@ use super::AppState; const PAGE_SIZE: usize = 25; +/// Activity tier for sorting project cards on the index page. +/// Lower values sort first (most active projects at the top). +fn project_card_activity_tier(card: &ProjectCard) -> u32 { + match card { + ProjectCard::Ok { + in_progress, open, .. + } => { + if *in_progress > 0 { + 0 // Has in-progress tasks + } else if *open > 0 { + 1 // Has open tasks only + } else { + 2 // Only closed or empty + } + } + ProjectCard::Err { .. } => 3, // Error state + } +} + pub(super) async fn index_handler(State(state): State) -> Response { let root = state.data_root.clone(); let result = tokio::task::spawn_blocking(move || -> Result { @@ -53,6 +72,28 @@ pub(super) async fn index_handler(State(state): State) -> Response { } } + // Sort cards by activity tier, then by name alphabetically + // Tier 0: in-progress tasks (most active) + // Tier 1: open tasks only + // Tier 2: closed/empty only + // Tier 3: errors + cards.sort_by(|a, b| { + let tier_a = project_card_activity_tier(a); + let tier_b = project_card_activity_tier(b); + match tier_a.cmp(&tier_b) { + std::cmp::Ordering::Equal => { + let name_a = match a { + ProjectCard::Ok { name, .. } | ProjectCard::Err { name, .. } => name, + }; + let name_b = match b { + ProjectCard::Ok { name, .. } | ProjectCard::Err { name, .. } => name, + }; + name_a.cmp(name_b) + } + other => other, + } + }); + Ok(IndexTemplate { all_projects: projects, active_project: None,