diff --git a/crates/spacesh-proto/src/event.rs b/crates/spacesh-proto/src/event.rs new file mode 100644 index 0000000..0a1ca41 --- /dev/null +++ b/crates/spacesh-proto/src/event.rs @@ -0,0 +1,76 @@ +use serde::{Deserialize, Serialize}; +use crate::ids::{SurfaceId, WorkspaceId}; + +/// The subset of activity that lands in the event log. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum EventKind { + Done, + Wait, + Error, + Exit, +} + +/// One logged event. Workspace name and agent label are denormalized so the +/// feed stays displayable after the surface or workspace is closed. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct EventRecord { + pub id: u64, + pub surface_id: SurfaceId, + pub workspace_id: WorkspaceId, + pub workspace_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub agent_label: Option, + pub kind: EventKind, + pub ts: u64, + pub read: bool, +} + +/// What a `mark_read` request targets. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "target", content = "value", rename_all = "snake_case")] +pub enum MarkReadTarget { + All, + Ids(Vec), + Surface(SurfaceId), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn event_kind_serializes_lowercase() { + assert_eq!(serde_json::to_string(&EventKind::Done).unwrap(), r#""done""#); + assert_eq!(serde_json::to_string(&EventKind::Exit).unwrap(), r#""exit""#); + } + + #[test] + fn event_record_round_trips() { + let r = EventRecord { + id: 7, + surface_id: SurfaceId("s_1".into()), + workspace_id: WorkspaceId("w_1".into()), + workspace_name: "infra".into(), + agent_label: Some("claude".into()), + kind: EventKind::Error, + ts: 1_700_000_000_000, + read: false, + }; + let back: EventRecord = serde_json::from_str(&serde_json::to_string(&r).unwrap()).unwrap(); + assert_eq!(back, r); + } + + #[test] + fn mark_read_target_variants_serialize() { + assert_eq!(serde_json::to_string(&MarkReadTarget::All).unwrap(), r#"{"target":"all"}"#); + assert_eq!( + serde_json::to_string(&MarkReadTarget::Ids(vec![1, 2])).unwrap(), + r#"{"target":"ids","value":[1,2]}"# + ); + let s = MarkReadTarget::Surface(SurfaceId("s_9".into())); + assert_eq!(serde_json::to_string(&s).unwrap(), r#"{"target":"surface","value":"s_9"}"#); + let back: MarkReadTarget = serde_json::from_str(&serde_json::to_string(&s).unwrap()).unwrap(); + assert_eq!(back, s); + } +} diff --git a/crates/spacesh-proto/src/lib.rs b/crates/spacesh-proto/src/lib.rs index 987b410..addc79f 100644 --- a/crates/spacesh-proto/src/lib.rs +++ b/crates/spacesh-proto/src/lib.rs @@ -1,10 +1,12 @@ pub mod codec; +pub mod event; pub mod ids; pub mod layout; pub mod message; pub mod status; pub mod workspace; +pub use event::{EventKind, EventRecord, MarkReadTarget}; pub use ids::{GroupId, SurfaceId, WorkspaceId}; pub use layout::{LayoutNode, Orient}; pub use message::{Cmd, Envelope, ErrorBody, Evt};