diff --git a/app/src-tauri/src/bridge.rs b/app/src-tauri/src/bridge.rs index 82c484d..1b20ade 100644 --- a/app/src-tauri/src/bridge.rs +++ b/app/src-tauri/src/bridge.rs @@ -269,14 +269,14 @@ pub async fn close_workspace(state: BridgeState<'_>, workspace_id: String) -> Re } #[tauri::command] -pub async fn set_workspace_meta(state: BridgeState<'_>, workspace_id: String, name: Option, group_id: Option, unread: Option, order: Option) -> Result { +pub async fn set_workspace_meta(state: BridgeState<'_>, workspace_id: String, name: Option, group_id: Option, unread: Option, order: Option, pinned: Option) -> Result { // group_id: None from JS means "no change"; an explicit null is sent as Some("") to mean "ungroup". let gid = match group_id { None => None, Some(s) if s.is_empty() => Some(None), Some(s) => Some(Some(GroupId(s))), }; - data_of(state.request(Cmd::SetWorkspaceMeta { workspace_id: WorkspaceId(workspace_id), name, group_id: gid, unread, order }).await.map_err(|e| e.to_string())?) + data_of(state.request(Cmd::SetWorkspaceMeta { workspace_id: WorkspaceId(workspace_id), name, group_id: gid, unread, order, pinned }).await.map_err(|e| e.to_string())?) } #[tauri::command] diff --git a/crates/spacesh-cli/src/mapping.rs b/crates/spacesh-cli/src/mapping.rs index 9c964d7..cd6e52a 100644 --- a/crates/spacesh-cli/src/mapping.rs +++ b/crates/spacesh-cli/src/mapping.rs @@ -61,6 +61,7 @@ pub fn to_cmd(sub: Sub) -> Cmd { group_id: group.map(|g| if g.is_empty() { None } else { Some(GroupId(g)) }), unread, order, + pinned: None, }, Sub::Shutdown => Cmd::Shutdown, Sub::Completions { .. } => unreachable!("completions handled before dispatch"), diff --git a/crates/spacesh-proto/src/message.rs b/crates/spacesh-proto/src/message.rs index ea38600..ed8015a 100644 --- a/crates/spacesh-proto/src/message.rs +++ b/crates/spacesh-proto/src/message.rs @@ -104,6 +104,8 @@ pub enum Cmd { unread: Option, #[serde(default, skip_serializing_if = "Option::is_none")] order: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pinned: Option, }, CreateGroup { name: String, color: String }, SetGroup { diff --git a/crates/spacesh-proto/src/workspace.rs b/crates/spacesh-proto/src/workspace.rs index 6dfc0bd..52e71c7 100644 --- a/crates/spacesh-proto/src/workspace.rs +++ b/crates/spacesh-proto/src/workspace.rs @@ -39,6 +39,9 @@ pub struct Workspace { pub order: u32, #[serde(default)] pub unread: bool, + /// Pinned to the sidebar's Favorites section. + #[serde(default)] + pub pinned: bool, /// None = empty workspace (no panels yet). #[serde(default)] pub layout: Option, @@ -68,6 +71,8 @@ pub struct WorkspaceView { pub group_id: Option, pub order: u32, pub unread: bool, + #[serde(default)] + pub pinned: bool, pub layout: Option, #[serde(default)] pub zoomed: Option, @@ -103,6 +108,7 @@ mod tests { group_id: None, order: 0, unread: false, + pinned: false, layout: None, zoomed: None, surfaces: HashMap::new(), @@ -112,11 +118,26 @@ mod tests { assert_eq!(back, w); } + #[test] + fn workspace_pinned_round_trips_and_defaults_false() { + let w = Workspace { + id: WorkspaceId("w_1".into()), path: "/tmp/p".into(), name: "p".into(), + group_id: None, order: 0, unread: false, pinned: true, layout: None, + zoomed: None, surfaces: HashMap::new(), + }; + let back: Workspace = serde_json::from_str(&serde_json::to_string(&w).unwrap()).unwrap(); + assert!(back.pinned); + // Old state.json without the field deserializes to pinned=false. + let legacy = r#"{"id":"w_1","path":"/tmp/p","name":"p","order":0,"surfaces":{}}"#; + let old: Workspace = serde_json::from_str(legacy).unwrap(); + assert!(!old.pinned); + } + #[test] fn workspace_round_trips_with_zoom() { let w = Workspace { id: WorkspaceId("w_1".into()), path: "/tmp/p".into(), name: "p".into(), - group_id: None, order: 0, unread: false, layout: None, + group_id: None, order: 0, unread: false, pinned: false, layout: None, zoomed: Some(SurfaceId("s_1".into())), surfaces: HashMap::new(), }; let back: Workspace = serde_json::from_str(&serde_json::to_string(&w).unwrap()).unwrap(); diff --git a/crates/spaceshd/src/registry.rs b/crates/spaceshd/src/registry.rs index ac9f679..a121cb3 100644 --- a/crates/spaceshd/src/registry.rs +++ b/crates/spaceshd/src/registry.rs @@ -51,7 +51,7 @@ impl Registry { let order = self.workspaces.len() as u32; self.workspaces.insert(id.clone(), Workspace { id: id.clone(), path: key.clone(), name, group_id: None, order, - unread: false, layout: None, zoomed: None, surfaces: HashMap::new(), + unread: false, pinned: false, layout: None, zoomed: None, surfaces: HashMap::new(), }); self.by_path.insert(key, id.clone()); (id, true) @@ -168,7 +168,7 @@ impl Registry { }).collect(); WorkspaceView { id: w.id.clone(), path: w.path.clone(), name: w.name.clone(), - group_id: w.group_id.clone(), order: w.order, unread: w.unread, + group_id: w.group_id.clone(), order: w.order, unread: w.unread, pinned: w.pinned, layout: w.layout.clone(), zoomed: w.zoomed.clone(), surfaces, } } diff --git a/crates/spaceshd/src/server.rs b/crates/spaceshd/src/server.rs index e5a9c2b..74ac084 100644 --- a/crates/spaceshd/src/server.rs +++ b/crates/spaceshd/src/server.rs @@ -461,12 +461,13 @@ async fn handle_request( let _ = out.send(ok(id, serde_json::Value::Null)).await; } - Cmd::SetWorkspaceMeta { workspace_id, name, group_id, unread, order } => { + Cmd::SetWorkspaceMeta { workspace_id, name, group_id, unread, order, pinned } => { let found = reg.workspace_mut(&workspace_id).map(|w| { if let Some(n) = name { w.name = n; } if let Some(g) = group_id { w.group_id = g; } if let Some(u) = unread { w.unread = u; } if let Some(o) = order { w.order = o; } + if let Some(p) = pinned { w.pinned = p; } }).is_some(); if found { if let Some(view) = reg.workspace_view(&workspace_id) { diff --git a/crates/spaceshd/src/state_store.rs b/crates/spaceshd/src/state_store.rs index 7a7c9b6..fe5951a 100644 --- a/crates/spaceshd/src/state_store.rs +++ b/crates/spaceshd/src/state_store.rs @@ -92,6 +92,7 @@ mod tests { group_id: None, order: 0, unread: false, + pinned: false, layout: None, zoomed: None, surfaces: std::collections::HashMap::new(),