From 114922aaf89a2a3ca52ae25522a337cc8c72378f Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Tue, 9 Jun 2026 21:11:51 +0700 Subject: [PATCH] feat(proto): GroupId, Orient, n-ary LayoutNode with external-tagged serde Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/spacesh-proto/src/ids.rs | 9 +++++ crates/spacesh-proto/src/layout.rs | 58 ++++++++++++++++++++++++++++++ crates/spacesh-proto/src/lib.rs | 4 ++- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 crates/spacesh-proto/src/layout.rs diff --git a/crates/spacesh-proto/src/ids.rs b/crates/spacesh-proto/src/ids.rs index ee733e8..6968ef2 100644 --- a/crates/spacesh-proto/src/ids.rs +++ b/crates/spacesh-proto/src/ids.rs @@ -16,3 +16,12 @@ impl std::fmt::Display for WorkspaceId { write!(f, "{}", self.0) } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct GroupId(pub String); + +impl std::fmt::Display for GroupId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/crates/spacesh-proto/src/layout.rs b/crates/spacesh-proto/src/layout.rs new file mode 100644 index 0000000..3080558 --- /dev/null +++ b/crates/spacesh-proto/src/layout.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; +use crate::ids::SurfaceId; + +/// Split orientation. `H` lays children left-to-right; `V` top-to-bottom. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Orient { + H, + V, +} + +/// Recursive n-ary layout tree. Externally tagged so JSON reads +/// `{ "leaf": { "surface_id": "s_1" } }` / `{ "split": { ... } }`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LayoutNode { + Leaf { surface_id: SurfaceId }, + Split { + orient: Orient, + ratios: Vec, + children: Vec, + }, +} + +impl LayoutNode { + pub fn leaf(id: SurfaceId) -> Self { + LayoutNode::Leaf { surface_id: id } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn leaf_serializes_externally_tagged() { + let n = LayoutNode::leaf(SurfaceId("s_1".into())); + let j = serde_json::to_string(&n).unwrap(); + assert_eq!(j, r#"{"leaf":{"surface_id":"s_1"}}"#); + } + + #[test] + fn split_round_trips() { + let n = LayoutNode::Split { + orient: Orient::V, + ratios: vec![0.5, 0.5], + children: vec![ + LayoutNode::leaf(SurfaceId("s_1".into())), + LayoutNode::leaf(SurfaceId("s_2".into())), + ], + }; + let j = serde_json::to_string(&n).unwrap(); + assert!(j.contains(r#""split""#)); + assert!(j.contains(r#""orient":"v""#)); + let back: LayoutNode = serde_json::from_str(&j).unwrap(); + assert_eq!(back, n); + } +} diff --git a/crates/spacesh-proto/src/lib.rs b/crates/spacesh-proto/src/lib.rs index 23ec789..328a525 100644 --- a/crates/spacesh-proto/src/lib.rs +++ b/crates/spacesh-proto/src/lib.rs @@ -1,6 +1,8 @@ pub mod codec; pub mod ids; +pub mod layout; pub mod message; -pub use ids::{SurfaceId, WorkspaceId}; +pub use ids::{GroupId, SurfaceId, WorkspaceId}; +pub use layout::{LayoutNode, Orient}; pub use message::{Cmd, Envelope, ErrorBody, Evt};