feat: scope Saved Queries panel to active connection + clean up on delete

Two changes that close the "everything lives in one global pool" gap.

UI: Saved Queries sidebar now defaults to "This connection" and lists
only entries with `connection_id == activeConnectionId` plus unattached
entries (legacy global saves before F2). A small toggle ("All") above
the search box brings the previous behavior back when copying queries
between databases. Each row in "All" mode shows a tag with the source
connection's name; legacy global entries show "unattached".

Backend: delete_connection now best-effort cleans persisted state for
that connection — removes `memory/<id>.md` (delete_memory_core in
memory.rs) and drops every entry in saved_queries.json with the
matching `connection_id` (delete_by_connection_core in
saved_queries.rs). Entries with `connection_id == None` are deliberately
preserved. Cleanup errors are logged but don't block the deletion since
connections.json is the source of truth.

Memory was already per-connection (F1); query history already filters
by connection. This commit makes saved queries behave the same and
stops orphan files from accumulating.
This commit is contained in:
2026-05-07 00:21:27 +03:00
parent 93e526af72
commit 95c9470411
4 changed files with 183 additions and 37 deletions

View File

@@ -118,6 +118,16 @@ pub async fn delete_connection(
fs::write(&path, data)?;
}
close_connection(&state, &id).await;
// Best-effort cleanup of per-connection persisted state; errors are logged
// but don't block the deletion (the connections.json is the source of truth).
if let Err(e) = crate::commands::memory::delete_memory_core(&app, &id) {
log::warn!("failed to delete memory file for {}: {}", id, e);
}
if let Err(e) =
crate::commands::saved_queries::delete_by_connection_core(&app, &id).await
{
log::warn!("failed to clean saved queries for {}: {}", id, e);
}
Ok(())
}

View File

@@ -146,6 +146,21 @@ pub(crate) fn enforce_size_cap(content: &str, cap: usize) -> String {
out
}
/// Best-effort delete of a connection's memory file. Returns Ok(()) when the
/// file doesn't exist; only surfaces an error for actual filesystem failures.
/// Used by delete_connection to keep memory/ from filling up with orphan files.
pub(crate) fn delete_memory_core(
app: &AppHandle,
connection_id: &str,
) -> TuskResult<()> {
let path = get_memory_path(app, connection_id)?;
if !path.exists() {
return Ok(());
}
fs::remove_file(&path)?;
Ok(())
}
#[tauri::command]
pub async fn get_memory(app: AppHandle, connection_id: String) -> TuskResult<String> {
read_memory_core(&app, &connection_id)

View File

@@ -54,6 +54,29 @@ pub(crate) async fn save_query_core(app: &AppHandle, query: SavedQuery) -> TuskR
Ok(())
}
/// Drop every saved-query entry tied to a specific connection_id. Entries with
/// `connection_id == None` (older "global" saves) are deliberately preserved
/// so they remain visible across all connections.
pub(crate) async fn delete_by_connection_core(
app: &AppHandle,
connection_id: &str,
) -> TuskResult<()> {
let path = get_saved_queries_path(app)?;
if !path.exists() {
return Ok(());
}
let data = fs::read_to_string(&path)?;
let mut entries: Vec<SavedQuery> = serde_json::from_str(&data).unwrap_or_default();
let before = entries.len();
entries.retain(|e| e.connection_id.as_deref() != Some(connection_id));
if entries.len() == before {
return Ok(());
}
let data = serde_json::to_string_pretty(&entries)?;
fs::write(&path, data)?;
Ok(())
}
#[tauri::command]
pub async fn list_saved_queries(
app: AppHandle,