use std::collections::HashMap; use serde::{Deserialize, Serialize}; use crate::ids::{GroupId, SurfaceId, WorkspaceId}; use crate::layout::LayoutNode; use crate::status::SurfaceState; /// Everything needed to (re)create a panel's process. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SurfaceSpec { pub command: String, #[serde(default)] pub args: Vec, pub cwd: String, #[serde(default)] pub agent_label: Option, pub cols: u16, pub rows: u16, #[serde(default)] pub autostart: bool, } /// A colored, ordered collection of workspaces. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Group { pub id: GroupId, pub name: String, pub color: String, pub order: u32, } /// Persisted workspace: structure only (no live process state). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Workspace { pub id: WorkspaceId, pub path: String, pub name: String, #[serde(default)] pub group_id: Option, pub order: u32, #[serde(default)] pub unread: bool, /// None = empty workspace (no panels yet). #[serde(default)] pub layout: Option, pub surfaces: HashMap, } /// Per-surface view in `status` — spec plus live lifecycle flag. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SurfaceView { pub spec: SurfaceSpec, /// true = has a live actor/PTY; false = stopped (in tree, no process). pub running: bool, /// Ephemeral agent-activity status (meaningful while running). #[serde(default)] pub state: SurfaceState, } /// Workspace view in `status` / `workspace_changed`: structure + per-surface state. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct WorkspaceView { pub id: WorkspaceId, pub path: String, pub name: String, pub group_id: Option, pub order: u32, pub unread: bool, pub layout: Option, pub surfaces: HashMap, } #[cfg(test)] mod tests { use super::*; #[test] fn surface_spec_round_trips() { let s = SurfaceSpec { command: "claude".into(), args: vec![], cwd: "/tmp".into(), agent_label: Some("claude".into()), cols: 80, rows: 24, autostart: false, }; let j = serde_json::to_string(&s).unwrap(); let back: SurfaceSpec = serde_json::from_str(&j).unwrap(); assert_eq!(back, s); } #[test] fn workspace_round_trips_with_empty_layout() { let w = Workspace { id: WorkspaceId("w_1".into()), path: "/tmp/p".into(), name: "p".into(), group_id: None, order: 0, unread: false, layout: None, surfaces: HashMap::new(), }; let j = serde_json::to_string(&w).unwrap(); let back: Workspace = serde_json::from_str(&j).unwrap(); assert_eq!(back, w); } }