feat(proto): SurfaceState + SetState command + State event + SurfaceView.state

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 22:10:54 +07:00
parent c2f8ef4214
commit 4bd4aa4a36
4 changed files with 67 additions and 0 deletions
+2
View File
@@ -2,9 +2,11 @@ pub mod codec;
pub mod ids;
pub mod layout;
pub mod message;
pub mod status;
pub mod workspace;
pub use ids::{GroupId, SurfaceId, WorkspaceId};
pub use layout::{LayoutNode, Orient};
pub use message::{Cmd, Envelope, ErrorBody, Evt};
pub use status::SurfaceState;
pub use workspace::{Group, SurfaceSpec, SurfaceView, Workspace, WorkspaceView};
+22
View File
@@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::ids::{GroupId, SurfaceId, WorkspaceId};
use crate::layout::LayoutNode;
use crate::status::SurfaceState;
use crate::workspace::{Group, WorkspaceView};
/// Wire envelope. `kind` is the serde tag.
@@ -114,6 +115,7 @@ pub enum Cmd {
order: Option<u32>,
},
DeleteGroup { group_id: GroupId },
SetState { surface_id: SurfaceId, state: SurfaceState },
Status,
Shutdown,
}
@@ -131,6 +133,7 @@ pub enum Evt {
WorkspaceClosed { workspace_id: WorkspaceId },
GroupsChanged { groups: Vec<Group> },
SurfaceRestarted { surface_id: SurfaceId },
State { surface_id: SurfaceId, state: SurfaceState },
}
#[cfg(test)]
@@ -240,4 +243,23 @@ mod tests {
let back: Envelope = serde_json::from_str(&serde_json::to_string(&evt).unwrap()).unwrap();
assert_eq!(back, evt);
}
#[test]
fn set_state_round_trips() {
let env = Envelope::Req {
id: 1,
cmd: Cmd::SetState { surface_id: SurfaceId("s_1".into()), state: crate::status::SurfaceState::Done },
};
let back: Envelope = serde_json::from_str(&serde_json::to_string(&env).unwrap()).unwrap();
assert_eq!(back, env);
}
#[test]
fn state_event_round_trips() {
let evt = Envelope::Evt(Evt::State { surface_id: SurfaceId("s_1".into()), state: crate::status::SurfaceState::Wait });
let j = serde_json::to_string(&evt).unwrap();
assert!(j.contains(r#""evt":"state""#));
let back: Envelope = serde_json::from_str(&j).unwrap();
assert_eq!(back, evt);
}
}
+39
View File
@@ -0,0 +1,39 @@
use serde::{Deserialize, Serialize};
/// Ephemeral agent-activity status of a running surface (orthogonal to the
/// running/stopped process lifecycle). Defaults to `Idle`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SurfaceState {
Work,
Wait,
Done,
Error,
#[default]
Idle,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serializes_snake_case() {
assert_eq!(serde_json::to_string(&SurfaceState::Work).unwrap(), r#""work""#);
assert_eq!(serde_json::to_string(&SurfaceState::Idle).unwrap(), r#""idle""#);
}
#[test]
fn default_is_idle() {
assert_eq!(SurfaceState::default(), SurfaceState::Idle);
}
#[test]
fn round_trips() {
for s in [SurfaceState::Work, SurfaceState::Wait, SurfaceState::Done, SurfaceState::Error, SurfaceState::Idle] {
let j = serde_json::to_string(&s).unwrap();
let back: SurfaceState = serde_json::from_str(&j).unwrap();
assert_eq!(back, s);
}
}
}
+4
View File
@@ -2,6 +2,7 @@ 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)]
@@ -50,6 +51,9 @@ 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.