feat(daemon): registry for workspaces and surfaces with idempotent open

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 19:58:28 +07:00
parent 32560ea364
commit 2aedc6924d
2 changed files with 106 additions and 0 deletions
+1
View File
@@ -1,4 +1,5 @@
mod lifecycle;
mod registry;
mod surface;
fn main() {
+105
View File
@@ -0,0 +1,105 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use spacesh_proto::{SurfaceId, WorkspaceId};
use crate::surface::SurfaceHandle;
#[derive(Clone)]
pub struct WorkspaceMeta {
pub id: WorkspaceId,
pub path: PathBuf,
}
/// Single-threaded owner of all live surfaces and workspaces.
/// Lives inside the server task; not shared across threads.
#[derive(Default)]
pub struct Registry {
counter: AtomicU64,
workspaces: HashMap<WorkspaceId, WorkspaceMeta>,
/// path → workspace, so `open` is idempotent.
by_path: HashMap<PathBuf, WorkspaceId>,
surfaces: HashMap<SurfaceId, SurfaceHandle>,
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
fn next_id(&self, prefix: &str) -> String {
let n = self.counter.fetch_add(1, Ordering::Relaxed);
format!("{prefix}_{n:x}")
}
/// Idempotent: opening the same canonicalized path returns the existing workspace.
pub fn open_workspace(&mut self, path: PathBuf) -> WorkspaceMeta {
let canonical = path.canonicalize().unwrap_or(path);
if let Some(id) = self.by_path.get(&canonical) {
return self.workspaces[id].clone();
}
let id = WorkspaceId(self.next_id("w"));
let meta = WorkspaceMeta { id: id.clone(), path: canonical.clone() };
self.workspaces.insert(id.clone(), meta.clone());
self.by_path.insert(canonical, id);
meta
}
pub fn workspace(&self, id: &WorkspaceId) -> Option<&WorkspaceMeta> {
self.workspaces.get(id)
}
pub fn new_surface_id(&self) -> SurfaceId {
SurfaceId(self.next_id("s"))
}
pub fn insert_surface(&mut self, handle: SurfaceHandle) {
self.surfaces.insert(handle.id.clone(), handle);
}
pub fn surface(&self, id: &SurfaceId) -> Option<&SurfaceHandle> {
self.surfaces.get(id)
}
pub fn remove_surface(&mut self, id: &SurfaceId) -> Option<SurfaceHandle> {
self.surfaces.remove(id)
}
/// Snapshot for the `status` command: (workspace, its surface ids).
pub fn status(&self) -> Vec<(WorkspaceMeta, Vec<SurfaceId>)> {
self.workspaces
.values()
.map(|w| {
let sids = self
.surfaces
.values()
.filter(|s| s.workspace_id == w.id)
.map(|s| s.id.clone())
.collect();
(w.clone(), sids)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_is_idempotent_per_path() {
let mut reg = Registry::new();
let dir = std::env::temp_dir();
let a = reg.open_workspace(dir.clone());
let b = reg.open_workspace(dir.clone());
assert_eq!(a.id, b.id);
}
#[test]
fn ids_are_unique_and_prefixed() {
let reg = Registry::new();
let s1 = reg.new_surface_id();
let s2 = reg.new_surface_id();
assert!(s1.0.starts_with("s_"));
assert_ne!(s1, s2);
}
}