use serde::{Deserialize, Serialize};
use crate::index::SessionIndex;
use crate::AttachPolicy;
use crate::DefinitionIndex;
use crate::NameStyle;
use crate::ProjectBinding;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PolicyMirror {
#[default]
AutoSwitch,
SuggestOnly,
PickerOnly,
}
impl From<AttachPolicy> for PolicyMirror {
fn from(p: AttachPolicy) -> Self {
match p {
AttachPolicy::AutoSwitch => PolicyMirror::AutoSwitch,
AttachPolicy::SuggestOnly => PolicyMirror::SuggestOnly,
AttachPolicy::PickerOnly => PolicyMirror::PickerOnly,
}
}
}
impl From<PolicyMirror> for AttachPolicy {
fn from(p: PolicyMirror) -> Self {
match p {
PolicyMirror::AutoSwitch => AttachPolicy::AutoSwitch,
PolicyMirror::SuggestOnly => AttachPolicy::SuggestOnly,
PolicyMirror::PickerOnly => AttachPolicy::PickerOnly,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct PracaSnapshot {
#[serde(default)]
pub index: SessionIndex,
#[serde(default)]
pub binding: ProjectBinding,
#[serde(default)]
pub definitions: DefinitionIndex,
#[serde(default)]
pub policy: PolicyMirror,
#[serde(default)]
pub name_style: NameStyle,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Praca, SessionRecord};
use ishou_tokens::SessionNameStyle;
use std::path::{Path, PathBuf};
use tear_types::id::SessionId;
fn sid(s: &str) -> SessionId {
SessionId::from_seed(s)
}
#[test]
fn policy_mirror_round_trips_every_variant() {
for p in [
AttachPolicy::AutoSwitch,
AttachPolicy::SuggestOnly,
AttachPolicy::PickerOnly,
] {
let mirror: PolicyMirror = p.into();
let back: AttachPolicy = mirror.into();
assert_eq!(p, back);
}
}
#[test]
fn praca_snapshot_round_trips_through_json() {
let mut p = Praca::new();
let rec = SessionRecord::for_project(
sid("tide"),
PathBuf::from("/code/pleme-io/mado"),
SessionNameStyle::Emoji,
1_700,
);
p.index.upsert(rec);
p.binding
.bind(PathBuf::from("/code/pleme-io/mado"), sid("tide"));
p.record_visit(sid("tide"), 1_999);
let snap = p.to_snapshot();
let json = serde_json::to_string(&snap).unwrap();
let back: PracaSnapshot = serde_json::from_str(&json).unwrap();
assert_eq!(snap, back);
let restored = Praca::from_snapshot(back);
assert_eq!(
restored.binding.lookup(Path::new("/code/pleme-io/mado")),
Some(sid("tide"))
);
let r = restored.index.get(sid("tide")).unwrap();
assert_eq!(r.last_seen, 1_999);
assert_eq!(r.visits, 2); }
#[test]
fn snapshot_persists_and_restores_the_latent_preset_catalog() {
use crate::{NameStyle, SessionDefinition};
let mut p = Praca::new();
p.definitions.upsert(SessionDefinition::single_pane(
"/code/pleme-io/substrate",
"/bin/zsh",
NameStyle::Emoji,
1_000,
));
let snap = p.to_snapshot();
let json = serde_json::to_string(&snap).unwrap();
let back: PracaSnapshot = serde_json::from_str(&json).unwrap();
let restored = Praca::from_snapshot(back);
assert_eq!(restored.definitions.len(), 1);
assert!(restored
.definitions
.by_project(Path::new("/code/pleme-io/substrate"))
.is_some());
}
#[test]
fn old_snapshot_without_definitions_loads_empty() {
let snap: PracaSnapshot = serde_json::from_str("{}").expect("empty snapshot loads");
assert!(snap.definitions.is_empty());
let p = Praca::from_snapshot(snap);
assert!(p.definitions.is_empty());
}
}