diff --git a/crates/spacesh-proto/src/lib.rs b/crates/spacesh-proto/src/lib.rs index 328a525..cef3834 100644 --- a/crates/spacesh-proto/src/lib.rs +++ b/crates/spacesh-proto/src/lib.rs @@ -2,7 +2,9 @@ pub mod codec; pub mod ids; pub mod layout; pub mod message; +pub mod workspace; pub use ids::{GroupId, SurfaceId, WorkspaceId}; pub use layout::{LayoutNode, Orient}; pub use message::{Cmd, Envelope, ErrorBody, Evt}; +pub use workspace::{Group, SurfaceSpec, SurfaceView, Workspace, WorkspaceView}; diff --git a/crates/spacesh-proto/src/workspace.rs b/crates/spacesh-proto/src/workspace.rs new file mode 100644 index 0000000..23ffea7 --- /dev/null +++ b/crates/spacesh-proto/src/workspace.rs @@ -0,0 +1,104 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; +use crate::ids::{GroupId, SurfaceId, WorkspaceId}; +use crate::layout::LayoutNode; + +/// 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, +} + +/// 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); + } +}