diff --git a/scripts/check.sh b/scripts/check.sh index 814d9c5b..7722f671 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -1,6 +1,9 @@ #!/bin/bash set -e +unset GIT_DIR GIT_INDEX_FILE GIT_WORK_TREE GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL \ + GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_PREFIX + echo "→ Ensuring MCP relay sidecar placeholder..." node src-tauri/scripts/copy-relay-sidecar.mjs @@ -19,4 +22,4 @@ pnpm test echo "→ Clippy..." cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings -echo "✓ All checks passed" +echo "✓ All checks passed" \ No newline at end of file diff --git a/src-tauri/src/bin/verun_mcp_relay.rs b/src-tauri/src/bin/verun_mcp_relay.rs index 4f0c575e..dd17713a 100644 --- a/src-tauri/src/bin/verun_mcp_relay.rs +++ b/src-tauri/src/bin/verun_mcp_relay.rs @@ -17,7 +17,9 @@ async fn main() -> ExitCode { use std::path::PathBuf; use verun_lib::mcp; - let task_id = std::env::var("VERUN_TASK_ID").ok().filter(|s| !s.is_empty()); + let task_id = std::env::var("VERUN_TASK_ID") + .ok() + .filter(|s| !s.is_empty()); let socket = match std::env::var("VERUN_MCP_SOCKET") { Ok(s) if !s.is_empty() => PathBuf::from(s), _ => { diff --git a/src-tauri/src/claude_jsonl.rs b/src-tauri/src/claude_jsonl.rs index 2d7faa9a..0f9b6af8 100644 --- a/src-tauri/src/claude_jsonl.rs +++ b/src-tauri/src/claude_jsonl.rs @@ -312,9 +312,13 @@ fn parse_transcript_user(value: &serde_json::Value) -> Vec { } match classify_envelope(s) { EnvelopeAction::Drop => return Vec::new(), - EnvelopeAction::Replace(text) => return vec![OutputItem::TranscriptUserMessage { text }], + EnvelopeAction::Replace(text) => { + return vec![OutputItem::TranscriptUserMessage { text }] + } EnvelopeAction::Keep => { - return vec![OutputItem::TranscriptUserMessage { text: s.to_string() }]; + return vec![OutputItem::TranscriptUserMessage { + text: s.to_string(), + }]; } } } @@ -335,7 +339,9 @@ fn parse_transcript_user(value: &serde_json::Value) -> Vec { } match classify_envelope(text) { EnvelopeAction::Drop => continue, - EnvelopeAction::Replace(t) => items.push(OutputItem::TranscriptUserMessage { text: t }), + EnvelopeAction::Replace(t) => { + items.push(OutputItem::TranscriptUserMessage { text: t }) + } EnvelopeAction::Keep => items.push(OutputItem::TranscriptUserMessage { text: text.to_string(), }), @@ -674,7 +680,9 @@ mod tests { let line = r#"{"type":"user","message":{"role":"user","content":"how do i write foo?"},"uuid":"u1"}"#; let items = parse_transcript_line(line); match items.as_slice() { - [OutputItem::TranscriptUserMessage { text }] => assert!(text.contains("how do i write")), + [OutputItem::TranscriptUserMessage { text }] => { + assert!(text.contains("how do i write")) + } other => panic!("expected UserMessage, got {other:?}"), } } @@ -785,7 +793,9 @@ mod tests { assert!( matches!(&items[0], OutputItem::UserAttachment { mime, data_b64 } if mime == "image/jpeg" && data_b64 == "AAAA") ); - assert!(matches!(&items[1], OutputItem::TranscriptUserMessage { text } if text == "check this")); + assert!( + matches!(&items[1], OutputItem::TranscriptUserMessage { text } if text == "check this") + ); } #[test] @@ -831,7 +841,9 @@ mod tests { // remaining 6 lines (queue-operation, 3 attachments, ai-title, // last-prompt) are all bookkeeping and produce nothing. assert_eq!(items.len(), 6, "items: {items:#?}"); - assert!(matches!(&items[0], OutputItem::TranscriptUserMessage { text } if text == "hello claude")); + assert!( + matches!(&items[0], OutputItem::TranscriptUserMessage { text } if text == "hello claude") + ); assert!( matches!(&items[1], OutputItem::Thinking { text } if text == "let me think about this") ); diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index 53ddfb85..b6d156cf 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -2093,10 +2093,9 @@ pub(crate) mod tests { .await .unwrap(); - let labels = - task_labels_for_ids(&pool, &["t-001".into(), "t-002".into()]) - .await - .unwrap(); + let labels = task_labels_for_ids(&pool, &["t-001".into(), "t-002".into()]) + .await + .unwrap(); assert_eq!( labels.get("t-001"), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 557afeef..57f50ca1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -215,16 +215,14 @@ pub fn run() { let pty_for_monitor = std::sync::Arc::clone(&*app.state::()); let pool_for_monitor = app.state::().inner().clone(); let monitor_handle = app.handle().clone(); - let sampler = std::sync::Arc::new( - crate::resource_monitor::ResourceSampler::spawn( - monitor_handle, - active_for_monitor, - lsp_for_monitor, - pty_for_monitor, - crate::resource_monitor::SysinfoSource::new(), - pool_for_monitor, - ), - ); + let sampler = std::sync::Arc::new(crate::resource_monitor::ResourceSampler::spawn( + monitor_handle, + active_for_monitor, + lsp_for_monitor, + pty_for_monitor, + crate::resource_monitor::SysinfoSource::new(), + pool_for_monitor, + )); app.manage(sampler); // Auto-check for updates after a short delay diff --git a/src-tauri/src/mcp.rs b/src-tauri/src/mcp.rs index 8f667f7e..3e1e7ad3 100644 --- a/src-tauri/src/mcp.rs +++ b/src-tauri/src/mcp.rs @@ -312,14 +312,9 @@ async fn tool_list_tasks(ctx: &McpContext, args: Value) -> Result limit; let visible: Vec<&db::ActiveTaskRow> = rows.iter().take(limit as usize).collect(); @@ -333,10 +328,7 @@ async fn tool_list_tasks(ctx: &McpContext, args: Value) -> Result = visible .iter() .map(|r| { - let display_name = r - .task_name - .clone() - .unwrap_or_else(|| r.branch.clone()); + let display_name = r.task_name.clone().unwrap_or_else(|| r.branch.clone()); json!({ "task_id": r.task_id, "name": display_name, @@ -385,16 +377,14 @@ async fn tool_list_sessions(ctx: &McpContext, args: Value) -> Result Result Result Result Result Result Result task's latest session's agent > project default. @@ -829,9 +808,8 @@ async fn tool_create_session(ctx: &McpContext, args: Value) -> Result { return Err(JsonRpcError { code: E_INVALID_PARAMS, - message: - "verun_create_session: 'initial_message' must not be empty when provided." - .into(), + message: "verun_create_session: 'initial_message' must not be empty when provided." + .into(), }); } Some(m) => Some(m.to_string()), @@ -840,9 +818,8 @@ async fn tool_create_session(ctx: &McpContext, args: Value) -> Result Result t, - None => ctx - .caller_task_id - .clone() - .ok_or_else(|| JsonRpcError { - code: E_INVALID_PARAMS, - message: "task_id is required when there is no caller task to infer one from." - .into(), - })?, + None => ctx.caller_task_id.clone().ok_or_else(|| JsonRpcError { + code: E_INVALID_PARAMS, + message: "task_id is required when there is no caller task to infer one from.".into(), + })?, }; db::get_task(&ctx.pool, &task_id) .await .map_err(internal)? .ok_or_else(|| JsonRpcError { code: E_INVALID_PARAMS, - message: format!( - "Task '{task_id}' not found. Use verun_list_tasks to find valid IDs." - ), + message: format!("Task '{task_id}' not found. Use verun_list_tasks to find valid IDs."), })?; Ok(task_id) } @@ -1071,8 +1042,7 @@ async fn tool_app_start(ctx: &McpContext, args: Value) -> Result Result Result { - let result = perform_spawn_task( - &app, - project_id, - base_branch, - agent_type, - initial_message, - ) - .await; + let result = + perform_spawn_task(&app, project_id, base_branch, agent_type, initial_message) + .await; let _ = reply.send(result); } McpAction::AppStart { task_id, reply } => { @@ -1294,14 +1257,8 @@ pub async fn run_action_worker(app: tauri::AppHandle, mut rx: mpsc::Receiver { - let result = perform_create_session( - &app, - task_id, - agent_type, - model, - initial_message, - ) - .await; + let result = + perform_create_session(&app, task_id, agent_type, model, initial_message).await; let _ = reply.send(result); } } @@ -1532,10 +1489,7 @@ async fn perform_app_start( }) } -async fn perform_app_stop( - app: &tauri::AppHandle, - task_id: &str, -) -> Result { +async fn perform_app_stop(app: &tauri::AppHandle, task_id: &str) -> Result { use tauri::Manager; let pool = app.state::(); @@ -1721,7 +1675,9 @@ fn find_start_command_pty(map: &crate::pty::ActivePtyMap, task_id: &str) -> Opti /// project's `.mcp.json` is never read or written. Claude Code picks this /// file up via `--mcp-config `. pub fn verun_mcp_config_path(app_data_dir: &Path, task_id: &str) -> PathBuf { - app_data_dir.join("mcp-configs").join(format!("{task_id}.json")) + app_data_dir + .join("mcp-configs") + .join(format!("{task_id}.json")) } /// Write the per-task verun MCP config file containing only the `verun` @@ -2016,8 +1972,7 @@ mod tests { } async fn insert_output(pool: &SqlitePool, session_id: &str, lines: &[&str]) { - let lines: Vec<(String, i64)> = - lines.iter().map(|l| ((*l).to_string(), 1000)).collect(); + let lines: Vec<(String, i64)> = lines.iter().map(|l| ((*l).to_string(), 1000)).collect(); process_write( pool, DbWrite::InsertOutputLines { @@ -2074,15 +2029,21 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-2", "App Two"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::InsertTask(task("t-b", "p-1", "beta", 2000))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-c", "p-2", "gamma", 3000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-c", "p-2", "gamma", 3000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), @@ -2114,12 +2075,18 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-2", "App Two"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-c", "p-2", "gamma", 3000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-c", "p-2", "gamma", 3000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), @@ -2298,9 +2265,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), @@ -2380,20 +2350,19 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), caller_task_id: Some("t-a".into()), actions: None, }; - let resp = dispatch( - &ctx, - call_list_tasks(json!({ "cursor": "not-a-number" })), - ) - .await; + let resp = dispatch(&ctx, call_list_tasks(json!({ "cursor": "not-a-number" }))).await; let p = extract_payload(&resp); // Garbage cursor is treated as "no cursor" - we get the full result rather // than failing the whole request. @@ -2430,12 +2399,17 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "Verun"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "fragile-stalestate-29", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "fragile-stalestate-29", 1000)), + ) + .await + .unwrap(); let mut t_named = task("t-b", "p-1", "frosty-bagel-42", 2000); t_named.name = Some("Add MCP server".into()); - process_write(&pool, DbWrite::InsertTask(t_named)).await.unwrap(); + process_write(&pool, DbWrite::InsertTask(t_named)) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), @@ -2541,12 +2515,18 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-2", "App Two"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-c", "p-2", "gamma", 2000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-c", "p-2", "gamma", 2000)), + ) + .await + .unwrap(); let dir = tempfile::tempdir().unwrap(); let socket = dir.path().join("v.sock"); @@ -2584,9 +2564,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let dir = tempfile::tempdir().unwrap(); let socket = dir.path().join("v.sock"); @@ -2714,7 +2697,10 @@ mod tests { // Server should drop the connection - read_line returns 0 bytes. let mut buf = String::new(); let n = reader.read_line(&mut buf).await.unwrap(); - assert_eq!(n, 0, "expected EOF after invalid identity frame, got {buf:?}"); + assert_eq!( + n, 0, + "expected EOF after invalid identity frame, got {buf:?}" + ); server.abort(); } @@ -3058,9 +3044,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let dir = tempfile::tempdir().unwrap(); let socket = dir.path().join("v.sock"); @@ -3116,15 +3105,21 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-2", "App Two"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::InsertTask(task("t-b", "p-1", "beta", 2000))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-c", "p-2", "gamma", 3000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-c", "p-2", "gamma", 3000)), + ) + .await + .unwrap(); let dir = tempfile::tempdir().unwrap(); let socket = dir.path().join("v.sock"); @@ -3186,7 +3181,10 @@ mod tests { .await .unwrap_err(); assert!( - matches!(err.kind(), std::io::ErrorKind::NotFound | std::io::ErrorKind::ConnectionRefused), + matches!( + err.kind(), + std::io::ErrorKind::NotFound | std::io::ErrorKind::ConnectionRefused + ), "unexpected error kind: {:?}", err.kind() ); @@ -3207,9 +3205,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-old", "t-a", 100))) .await .unwrap(); @@ -3224,11 +3225,7 @@ mod tests { caller_task_id: Some("t-a".into()), actions: None, }; - let resp = dispatch( - &ctx, - call_read_task_output(json!({ "task_id": "t-a" })), - ) - .await; + let resp = dispatch(&ctx, call_read_task_output(json!({ "task_id": "t-a" }))).await; let payload = extract_payload(&resp); assert_eq!(payload["session_id"], "s-new"); @@ -3248,9 +3245,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::InsertTask(task("t-b", "p-1", "beta", 2000))) .await .unwrap(); @@ -3281,11 +3281,7 @@ mod tests { caller_task_id: None, actions: None, }; - let resp = dispatch( - &ctx, - call_read_task_output(json!({ "task_id": "ghost" })), - ) - .await; + let resp = dispatch(&ctx, call_read_task_output(json!({ "task_id": "ghost" }))).await; let err = resp.error.unwrap(); assert_eq!(err.code, E_INVALID_PARAMS); assert!(err.message.contains("verun_list_tasks")); @@ -3297,19 +3293,18 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), caller_task_id: None, actions: None, }; - let resp = dispatch( - &ctx, - call_read_task_output(json!({ "task_id": "t-a" })), - ) - .await; + let resp = dispatch(&ctx, call_read_task_output(json!({ "task_id": "t-a" }))).await; let err = resp.error.unwrap(); assert_eq!(err.code, E_INVALID_PARAMS); assert!(err.message.contains("no sessions")); @@ -3321,9 +3316,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), @@ -3360,9 +3358,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-a", "t-a", 100))) .await .unwrap(); @@ -3416,9 +3417,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-a", "t-a", 100))) .await .unwrap(); @@ -3462,11 +3466,7 @@ mod tests { caller_task_id: None, actions: None, }; - let resp = dispatch( - &ctx, - call_read_task_output(json!({ "task_id": "t-a" })), - ) - .await; + let resp = dispatch(&ctx, call_read_task_output(json!({ "task_id": "t-a" }))).await; let p = extract_payload(&resp); assert_eq!(p["task_id"], "t-a"); assert_eq!(p["task_name"], "Fix Auth"); @@ -3524,10 +3524,7 @@ mod tests { message, reply, } => { - captured_for_worker - .lock() - .await - .push((session_id, message)); + captured_for_worker.lock().await.push((session_id, message)); let _ = reply.send(reply_with.clone()); } McpAction::SpawnTask { reply, .. } => { @@ -3557,9 +3554,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-old", "t-a", 100))) .await .unwrap(); @@ -3595,9 +3595,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-old", "t-a", 100))) .await .unwrap(); @@ -3633,9 +3636,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::InsertTask(task("t-b", "p-1", "beta", 2000))) .await .unwrap(); @@ -3695,9 +3701,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _) = spawn_capture_worker(Ok(())); let ctx = McpContext { @@ -3726,9 +3735,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _) = spawn_capture_worker(Ok(())); let ctx = McpContext { @@ -3805,9 +3817,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-a", "t-a", 100))) .await .unwrap(); @@ -3832,9 +3847,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-a", "t-a", 100))) .await .unwrap(); @@ -3961,9 +3979,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-2", "App Two"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, captured) = spawn_capture_spawn_worker(outcome("t-new", "s-new")); let ctx = McpContext { @@ -3993,9 +4014,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-2", "App Two"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, captured) = spawn_capture_spawn_worker(outcome("t-new", "s-new")); let ctx = McpContext { @@ -4036,11 +4060,7 @@ mod tests { caller_task_id: None, actions: Some(tx), }; - let resp = dispatch( - &ctx, - call_spawn_task(json!({ "project_id": "p-ghost" })), - ) - .await; + let resp = dispatch(&ctx, call_spawn_task(json!({ "project_id": "p-ghost" }))).await; let err = resp.error.expect("expected error"); assert_eq!(err.code, E_INVALID_PARAMS); assert!(err.message.contains("p-ghost")); @@ -4052,9 +4072,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, captured) = spawn_capture_spawn_worker(outcome("t-new", "s-new")); let ctx = McpContext { @@ -4073,9 +4096,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _) = spawn_capture_spawn_worker(outcome("t-new", "s-new")); let ctx = McpContext { @@ -4099,9 +4125,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, captured) = spawn_capture_spawn_worker(outcome("t-new", "s-new")); let ctx = McpContext { @@ -4109,11 +4138,7 @@ mod tests { caller_task_id: Some("t-a".into()), actions: Some(tx), }; - let _ = dispatch( - &ctx, - call_spawn_task(json!({ "base_branch": "develop" })), - ) - .await; + let _ = dispatch(&ctx, call_spawn_task(json!({ "base_branch": "develop" }))).await; let calls = captured.lock().await; assert_eq!(calls[0].base_branch.as_deref(), Some("develop")); } @@ -4124,9 +4149,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let mut o = outcome("t-new", "s-new"); o.initial_message_delivered = true; @@ -4154,9 +4182,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _) = spawn_capture_spawn_worker(outcome("t-new", "s-new")); let ctx = McpContext { @@ -4164,16 +4195,11 @@ mod tests { caller_task_id: Some("t-a".into()), actions: Some(tx), }; - let resp = dispatch( - &ctx, - call_spawn_task(json!({ "initial_message": " " })), - ) - .await; + let resp = dispatch(&ctx, call_spawn_task(json!({ "initial_message": " " }))).await; let err = resp.error.expect("expected error"); assert_eq!(err.code, E_INVALID_PARAMS); assert!( - err.message.to_lowercase().contains("empty") - || err.message.contains("initial_message") + err.message.to_lowercase().contains("empty") || err.message.contains("initial_message") ); } @@ -4183,9 +4209,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), @@ -4203,9 +4232,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, mut rx) = mpsc::channel::(4); tokio::spawn(async move { @@ -4232,9 +4264,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _) = spawn_capture_spawn_worker(SpawnTaskOutcome { task_id: "t-new".into(), @@ -4393,9 +4428,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (start, stop, logs) = default_outcomes(); let (tx, captured) = spawn_capture_app_worker(start, stop, logs); @@ -4419,9 +4457,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::InsertTask(task("t-b", "p-1", "beta", 2000))) .await .unwrap(); @@ -4475,9 +4516,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let ctx = McpContext { pool: pool.clone(), caller_task_id: Some("t-a".into()), @@ -4494,9 +4538,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (mut start, stop, logs) = default_outcomes(); start.already_running = true; @@ -4519,9 +4566,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (start, stop, logs) = default_outcomes(); let (tx, captured) = spawn_capture_app_worker(start, stop, logs); @@ -4544,9 +4594,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (start, mut stop, logs) = default_outcomes(); stop.stopped = false; @@ -4587,9 +4640,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (start, stop, logs) = default_outcomes(); let (tx, captured) = spawn_capture_app_worker(start, stop, logs); @@ -4616,9 +4672,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (start, stop, logs) = default_outcomes(); let (tx, captured) = spawn_capture_app_worker(start, stop, logs); @@ -4644,9 +4703,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (start, stop, mut logs) = default_outcomes(); logs.running = false; @@ -4769,13 +4831,19 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); // running session - process_write(&pool, DbWrite::CreateSession(session("s-running", "t-a", 2000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::CreateSession(session("s-running", "t-a", 2000)), + ) + .await + .unwrap(); // closed session let mut closed = session("s-closed", "t-a", 1500); closed.status = "closed".into(); @@ -4799,10 +4867,9 @@ mod tests { actions: None, }; let resp = dispatch(&ctx, call_list_sessions(json!({}))).await; - let payload: Value = serde_json::from_str( - resp.result.unwrap()["content"][0]["text"].as_str().unwrap(), - ) - .unwrap(); + let payload: Value = + serde_json::from_str(resp.result.unwrap()["content"][0]["text"].as_str().unwrap()) + .unwrap(); let items = payload["items"].as_array().unwrap(); assert_eq!(items.len(), 1); assert_eq!(items[0]["session_id"], "s-running"); @@ -4819,9 +4886,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); process_write(&pool, DbWrite::CreateSession(session("s-r", "t-a", 2000))) .await .unwrap(); @@ -4843,14 +4913,15 @@ mod tests { caller_task_id: Some("t-a".into()), actions: None, }; - let resp = - dispatch(&ctx, call_list_sessions(json!({ "include_closed": true }))).await; - let payload: Value = serde_json::from_str( - resp.result.unwrap()["content"][0]["text"].as_str().unwrap(), - ) - .unwrap(); + let resp = dispatch(&ctx, call_list_sessions(json!({ "include_closed": true }))).await; + let payload: Value = + serde_json::from_str(resp.result.unwrap()["content"][0]["text"].as_str().unwrap()) + .unwrap(); let items = payload["items"].as_array().unwrap(); - let ids: Vec<&str> = items.iter().map(|i| i["session_id"].as_str().unwrap()).collect(); + let ids: Vec<&str> = items + .iter() + .map(|i| i["session_id"].as_str().unwrap()) + .collect(); assert!(ids.contains(&"s-r")); assert!(ids.contains(&"s-c")); } @@ -4863,11 +4934,7 @@ mod tests { caller_task_id: None, actions: None, }; - let resp = dispatch( - &ctx, - call_list_sessions(json!({ "task_id": "t-nope" })), - ) - .await; + let resp = dispatch(&ctx, call_list_sessions(json!({ "task_id": "t-nope" }))).await; let err = resp.error.unwrap(); assert_eq!(err.code, E_INVALID_PARAMS); assert!(err.message.contains("verun_list_tasks")); @@ -4925,20 +4992,19 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _rx) = mpsc::channel::(1); let ctx = McpContext { pool, caller_task_id: Some("t-a".into()), actions: Some(tx), }; - let resp = dispatch( - &ctx, - call_create_session(json!({ "agent_type": "bogus" })), - ) - .await; + let resp = dispatch(&ctx, call_create_session(json!({ "agent_type": "bogus" }))).await; let err = resp.error.unwrap(); assert_eq!(err.code, E_INVALID_PARAMS); assert!(err.message.contains("unknown agent_type")); @@ -4950,9 +5016,12 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); let (tx, _rx) = mpsc::channel::(1); let ctx = McpContext { pool, @@ -4978,11 +5047,7 @@ mod tests { caller_task_id: None, actions: Some(tx), }; - let resp = dispatch( - &ctx, - call_create_session(json!({ "task_id": "t-nope" })), - ) - .await; + let resp = dispatch(&ctx, call_create_session(json!({ "task_id": "t-nope" }))).await; let err = resp.error.unwrap(); assert_eq!(err.code, E_INVALID_PARAMS); assert!(err.message.contains("verun_list_tasks")); @@ -4994,12 +5059,18 @@ mod tests { process_write(&pool, DbWrite::InsertProject(project("p-1", "App One"))) .await .unwrap(); - process_write(&pool, DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000))) - .await - .unwrap(); - process_write(&pool, DbWrite::CreateSession(session("s-prev", "t-a", 1500))) - .await - .unwrap(); + process_write( + &pool, + DbWrite::InsertTask(task("t-a", "p-1", "alpha", 1000)), + ) + .await + .unwrap(); + process_write( + &pool, + DbWrite::CreateSession(session("s-prev", "t-a", 1500)), + ) + .await + .unwrap(); let (tx, mut rx) = mpsc::channel::(2); tokio::spawn(async move { @@ -5033,10 +5104,9 @@ mod tests { call_create_session(json!({ "initial_message": "hello fresh" })), ) .await; - let payload: Value = serde_json::from_str( - resp.result.unwrap()["content"][0]["text"].as_str().unwrap(), - ) - .unwrap(); + let payload: Value = + serde_json::from_str(resp.result.unwrap()["content"][0]["text"].as_str().unwrap()) + .unwrap(); assert_eq!(payload["task_id"], "t-a"); assert_eq!(payload["session_id"], "s-new"); assert_eq!(payload["initial_message_delivered"], true); diff --git a/src-tauri/src/resource_monitor.rs b/src-tauri/src/resource_monitor.rs index 725f5b76..eebf10b9 100644 --- a/src-tauri/src/resource_monitor.rs +++ b/src-tauri/src/resource_monitor.rs @@ -72,9 +72,13 @@ pub fn attribute( let mut seen = HashSet::new(); let mut stack = vec![root]; while let Some(p) = stack.pop() { - if !seen.insert(p) { continue; } + if !seen.insert(p) { + continue; + } if let Some(kids) = children.get(&p) { - for &k in kids { stack.push(k); } + for &k in kids { + stack.push(k); + } } } seen @@ -84,7 +88,9 @@ pub fn attribute( let mut task_totals: HashMap = HashMap::new(); for (task_id, session_pid) in active { - if !by_pid.contains_key(session_pid) { continue; } + if !by_pid.contains_key(session_pid) { + continue; + } let subtree = descendants_inclusive(*session_pid); let mut subtree_rss = 0u64; let mut subtree_cpu = 0f32; @@ -107,7 +113,9 @@ pub fn attribute( let mut app_rss = 0u64; let mut app_cpu = 0f32; for pid in &verun_subtree { - if attributed.contains(pid) { continue; } + if attributed.contains(pid) { + continue; + } if let Some(row) = by_pid.get(pid) { app_rss += row.rss_bytes; app_cpu += row.cpu_pct; @@ -131,14 +139,19 @@ pub fn attribute( } }) .collect(); - tasks.sort_by(|a, b| b.rss_bytes.cmp(&a.rss_bytes)); - + tasks.sort_by_key(|t| std::cmp::Reverse(t.rss_bytes)); let total_rss = app_rss + tasks.iter().map(|t| t.rss_bytes).sum::(); let total_cpu = app_cpu + tasks.iter().map(|t| t.cpu_pct).sum::(); Sample { - total: ProcessStat { rss_bytes: total_rss, cpu_pct: total_cpu }, - app: ProcessStat { rss_bytes: app_rss, cpu_pct: app_cpu }, + total: ProcessStat { + rss_bytes: total_rss, + cpu_pct: total_cpu, + }, + app: ProcessStat { + rss_bytes: app_rss, + cpu_pct: app_cpu, + }, tasks, sampled_at_ms, } @@ -162,7 +175,9 @@ impl SysinfoSource { } impl Default for SysinfoSource { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } impl ProcessSource for SysinfoSource { @@ -185,8 +200,8 @@ impl ProcessSource for SysinfoSource { } } -use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; pub struct ResourceSampler { cadence: Arc, @@ -243,8 +258,7 @@ impl ResourceSampler { loop { let active_pairs = task_root_pids(&active, &lsp_map, &pty_map); - let task_ids: Vec = - active_pairs.iter().map(|(t, _)| t.clone()).collect(); + let task_ids: Vec = active_pairs.iter().map(|(t, _)| t.clone()).collect(); let labels = crate::db::task_labels_for_ids(&pool, &task_ids) .await .unwrap_or_else(|e| { @@ -350,7 +364,12 @@ mod tests { use super::*; fn row(pid: u32, parent: Option, rss: u64, cpu: f32) -> ProcRow { - ProcRow { pid, parent_pid: parent, rss_bytes: rss, cpu_pct: cpu } + ProcRow { + pid, + parent_pid: parent, + rss_bytes: rss, + cpu_pct: cpu, + } } fn labels(pairs: &[(&str, &str, &str)]) -> HashMap { @@ -454,11 +473,7 @@ mod tests { row(20, Some(1), 500, 0.0), row(30, Some(1), 250, 0.0), ]; - let active = vec![ - ("small".into(), 10), - ("big".into(), 20), - ("mid".into(), 30), - ]; + let active = vec![("small".into(), 10), ("big".into(), 20), ("mid".into(), 30)]; let labels = labels(&[ ("small", "S", "br-s"), ("big", "B", "br-b"), @@ -489,7 +504,9 @@ mod tests { let mut src = SysinfoSource::new(); let snap = src.snapshot(); let self_pid = std::process::id(); - let me = snap.iter().find(|r| r.pid == self_pid) + let me = snap + .iter() + .find(|r| r.pid == self_pid) .expect("self process must be in the snapshot"); assert!(me.rss_bytes > 0, "self process RSS should be > 0"); } diff --git a/src-tauri/src/task.rs b/src-tauri/src/task.rs index 78b3d1ec..5df34a66 100644 --- a/src-tauri/src/task.rs +++ b/src-tauri/src/task.rs @@ -973,14 +973,12 @@ pub async fn create_task( let socket_path = mcp::socket_path(&app_data); match mcp::relay_binary_path() { Ok(relay) => { - if let Err(e) = - mcp::write_verun_mcp_config(&app_data, &id, &socket_path, &relay) - { + if let Err(e) = mcp::write_verun_mcp_config(&app_data, &id, &socket_path, &relay) { eprintln!("[verun] failed to write verun mcp config for task {id}: {e}"); } - if let Err(e) = mcp::pre_approve_verun_in_claude_settings( - std::path::Path::new(&worktree_path), - ) { + if let Err(e) = + mcp::pre_approve_verun_in_claude_settings(std::path::Path::new(&worktree_path)) + { eprintln!("[verun] failed to pre-approve verun in claude settings: {e}"); } } @@ -1206,28 +1204,26 @@ pub async fn archive_task( let wtp = task.worktree_path.clone(); let env_vars = worktree::verun_env_vars(task.port_offset, repo_path); let task_id = task.id.clone(); - let db_tx_bg = db_tx.clone(); - tokio::spawn(async move { - let last_commit_message = tokio::task::spawn_blocking(move || { - if !hook.is_empty() { - if let Err(e) = worktree::run_hook(&wtp, &hook, &env_vars) { - eprintln!("[verun] destroy hook failed: {e}"); - } - } - worktree::last_commit_message(&rp, &branch) - }) - .await - .unwrap_or(None); - if last_commit_message.is_some() { - let _ = db_tx_bg - .send(db::DbWrite::SetLastCommitMessage { - id: task_id, - msg: last_commit_message, - }) - .await; + let last_commit_message = tokio::task::spawn_blocking(move || { + if !hook.is_empty() { + if let Err(e) = worktree::run_hook(&wtp, &hook, &env_vars) { + eprintln!("[verun] destroy hook failed: {e}"); + } } - }); + worktree::last_commit_message(&rp, &branch) + }) + .await + .unwrap_or(None); + + if last_commit_message.is_some() { + let _ = db_tx + .send(db::DbWrite::SetLastCommitMessage { + id: task_id, + msg: last_commit_message, + }) + .await; + } Ok(()) } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 8c09e0e1..d99b098f 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -43,7 +43,7 @@ import { } from "../store/ui"; import { sessions, loadSessions } from "../store/sessions"; import { isStartCommandRunning } from "../store/terminals"; -import { deleteProject } from "../store/projects"; +import { deleteProject, isProjectDeleting } from "../store/projects"; import { ConfirmDialog } from "./ConfirmDialog"; import { ContextMenu, type ContextMenuItem } from "./ContextMenu"; import { buildAddProjectMenuItems } from "../lib/addProjectMenu"; @@ -375,8 +375,10 @@ export const Sidebar: Component = () => { {/* Project + task list */}
- {(project) => ( -
+ {(project) => { + const deleting = () => isProjectDeleting(project.id); + return ( +
showProjectMenu(e, project.id)} @@ -389,10 +391,19 @@ export const Sidebar: Component = () => { color: projectColor(project.id), }} > - {project.name.charAt(0)} + + + - - {project.name} + + + {project.name} + + + + Deleting… + +
@@ -529,7 +546,8 @@ export const Sidebar: Component = () => {
- )} + ); + }}
diff --git a/src/store/projects.ts b/src/store/projects.ts index 8047fa17..c372f533 100644 --- a/src/store/projects.ts +++ b/src/store/projects.ts @@ -1,3 +1,4 @@ +import { createSignal } from 'solid-js' import { createStore, produce } from 'solid-js/store' import { listen } from '@tauri-apps/api/event' import type { Project } from '../types' @@ -8,6 +9,15 @@ import { clearRecentFilesForProject } from './recentFiles' export const [projects, setProjects] = createStore([]) +const [deletingProjects, setDeletingProjects] = createSignal>(new Set()) +export const isProjectDeleting = (id: string) => deletingProjects().has(id) +function addDeleting(id: string) { + setDeletingProjects(prev => new Set([...prev, id])) +} +function removeDeleting(id: string) { + setDeletingProjects(prev => { const s = new Set(prev); s.delete(id); return s }) +} + export async function loadProjects() { const list = await ipc.listProjects() setProjects(list) @@ -20,21 +30,26 @@ export async function addProject(repoPath: string): Promise { } export async function deleteProject(id: string) { - await ipc.deleteProject(id) - setProjects(prev => prev.filter(p => p.id !== id)) - clearRecentFilesForProject(id) + addDeleting(id) + try { + await ipc.deleteProject(id) + setProjects(prev => prev.filter(p => p.id !== id)) + clearRecentFilesForProject(id) - // Clear selection if the selected task belongs to the deleted project - const tid = selectedTaskId() - if (tid) { - const task = taskById(tid) - if (!task || task.projectId === id) { - setSelectedSessionIdForTask(tid, null) - setSelectedTaskId(null) + // Clear selection if the selected task belongs to the deleted project + const tid = selectedTaskId() + if (tid) { + const task = taskById(tid) + if (!task || task.projectId === id) { + setSelectedSessionIdForTask(tid, null) + setSelectedTaskId(null) + } } - } - if (selectedProjectId() === id) { - setSelectedProjectId(null) + if (selectedProjectId() === id) { + setSelectedProjectId(null) + } + } finally { + removeDeleting(id) } } diff --git a/src/store/tasks.ts b/src/store/tasks.ts index 1151be57..2b52811d 100644 --- a/src/store/tasks.ts +++ b/src/store/tasks.ts @@ -52,8 +52,19 @@ export function clearTaskError(id: string) { export async function loadTasks(projectId: string) { const list = await ipc.listTasks(projectId) - // Replace tasks for this project, keep tasks from other projects - setTasks(prev => [...prev.filter(t => t.projectId !== projectId), ...list]) + setTasks(prev => { + // Preserve the existing in-store order for tasks we already know about + // (so a reload while archiving doesn't shuffle the in-flight row to the + // bottom via DB's ORDER BY archived ASC). New tasks from the DB are + // appended at the end in their DB order. + const incoming = new Map(list.map(t => [t.id, t])) + const preserved = prev + .filter(t => t.projectId === projectId && incoming.has(t.id)) + .map(t => incoming.get(t.id)!) + const preservedIds = new Set(preserved.map(t => t.id)) + const added = list.filter(t => !preservedIds.has(t.id)) + return [...prev.filter(t => t.projectId !== projectId), ...preserved, ...added] + }) } export const activeTasks = () => @@ -63,7 +74,7 @@ export const tasksForProject = (projectId: string) => tasks.filter(t => t.projectId === projectId) export const activeTasksForProject = (projectId: string) => - tasks.filter(t => t.projectId === projectId && !t.archived) + tasks.filter(t => t.projectId === projectId && (!t.archived || isTaskArchiving(t.id))) export const archivedTasksForProject = (projectId: string) => tasks.filter(t => t.projectId === projectId && t.archived)