use super::UiState;
use serde_json::Value;
impl UiState {
pub fn prune_workspace(&mut self, key: &str) {
if let Some(Value::Object(seen)) = self.world.root.get_mut("seen") {
seen.remove(key);
}
drop_from(&mut self.world.root, "pinned", key);
self.world.save();
}
pub fn prune_agent(&mut self, key: &str, root: &str) {
if let Some(Value::Object(seen)) = self.world.root.get_mut("seen")
&& let Some(Value::Object(by_agent)) = seen.get_mut(key)
{
let prefix = format!("{root}-");
by_agent.retain(|id, _| id != root && !id.starts_with(&prefix));
}
self.world.save();
}
}
fn drop_from(root: &mut serde_json::Map<String, Value>, field: &str, value: &str) {
let Some(Value::Array(arr)) = root.get_mut(field) else {
return;
};
arr.retain(|v| v.as_str() != Some(value));
}
#[cfg(test)]
mod tests {
use crate::ui_state::{SeenKind, UiState};
use tempfile::tempdir;
const WS: &str = "/y/workspaces/alba-koi";
const OTHER: &str = "/y/workspaces/zeta-pug";
fn seeded(path: std::path::PathBuf) -> UiState {
let mut ui = UiState::open(path);
ui.record_seen(WS, "c-1", &[(SeenKind::Notify, "oid1".to_owned())]);
ui.record_seen(OTHER, "c-9", &[(SeenKind::Notify, "oid9".to_owned())]);
ui.set_pinned(vec![WS.to_owned(), OTHER.to_owned()]);
ui
}
#[test]
fn the_prune_drops_seen_and_pin_and_spares_every_other_workspace() {
let dir = tempdir().unwrap();
let path = dir.path().join("ui.json");
let mut ui = seeded(path.clone());
ui.prune_workspace(WS);
assert!(!ui.is_seen(SeenKind::Notify, WS, "c-1", "oid1"));
assert_eq!(ui.pinned(), [OTHER.to_owned()]);
assert!(ui.is_seen(SeenKind::Notify, OTHER, "c-9", "oid9"));
let reread = UiState::open(path);
assert_eq!(reread.pinned(), [OTHER.to_owned()]);
assert!(!reread.is_seen(SeenKind::Notify, WS, "c-1", "oid1"));
}
#[test]
fn the_agent_prune_takes_the_subtree_on_the_hyphen_boundary_and_nothing_else() {
let dir = tempdir().unwrap();
let path = dir.path().join("ui.json");
let mut ui = UiState::open(path.clone());
for id in ["r-aa", "r-aa-c-bb", "r-aab", "z-zz"] {
ui.record_seen(WS, id, &[(SeenKind::Notify, format!("oid-{id}"))]);
}
ui.record_seen(OTHER, "r-aa", &[(SeenKind::Notify, "elsewhere".to_owned())]);
ui.prune_agent(WS, "r-aa");
assert!(!ui.is_seen(SeenKind::Notify, WS, "r-aa", "oid-r-aa"));
assert!(!ui.is_seen(SeenKind::Notify, WS, "r-aa-c-bb", "oid-r-aa-c-bb"));
assert!(ui.is_seen(SeenKind::Notify, WS, "r-aab", "oid-r-aab"));
assert!(ui.is_seen(SeenKind::Notify, WS, "z-zz", "oid-z-zz"));
assert!(ui.is_seen(SeenKind::Notify, OTHER, "r-aa", "elsewhere"));
let reread = UiState::open(path);
assert!(!reread.is_seen(SeenKind::Notify, WS, "r-aa", "oid-r-aa"));
}
#[test]
fn pruning_an_agent_nobody_acknowledged_adds_no_keys() {
let dir = tempdir().unwrap();
let path = dir.path().join("ui.json");
let mut ui = UiState::open(path.clone());
ui.prune_agent(WS, "r-aa");
let doc = String::from_utf8(std::fs::read(&path).unwrap()).unwrap();
assert!(
!doc.contains("seen"),
"a subtraction, never a schema seeding"
);
}
#[test]
fn pruning_a_workspace_that_owns_nothing_adds_no_keys() {
let dir = tempdir().unwrap();
let path = dir.path().join("ui.json");
let mut ui = UiState::open(path.clone());
ui.prune_workspace(WS);
assert!(ui.pinned().is_empty());
let doc = String::from_utf8(std::fs::read(&path).unwrap()).unwrap();
assert!(!doc.contains("pinned") && !doc.contains("seen"));
}
}