Skip to main content

systemprompt_models/documents/
awareness.rs

1//! Awareness: the ephemeral state each participant of a live document
2//! shares with the others (who they are, their colour, caret, selection and
3//! viewport), and the live channel's own control events.
4//!
5//! Positions are engine cursors encoded by the client's CRDT engine
6//! (`anchor` and `head` are opaque base64 strings tied to a block id), so
7//! they survive concurrent edits; the server stores and relays them but
8//! never decodes them. An awareness state expires `ttl` after it was sent.
9//!
10//! Copyright (c) systemprompt.io — Business Source License 1.1.
11//! See <https://systemprompt.io> for licensing details.
12
13use chrono::{DateTime, Utc};
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use systemprompt_identifiers::{DocumentEditSessionId, DocumentId, DocumentNodeId, UserId};
17
18pub const AWARENESS_COLOR_MAX: usize = 32;
19pub const AWARENESS_CURSOR_MAX: usize = 512;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
22#[serde(rename_all = "snake_case")]
23pub enum AwarenessMode {
24    Viewing,
25    Editing,
26    Commenting,
27    Presenting,
28}
29
30/// A caret or selection: `anchor` and `head` are engine cursors inside the
31/// block, cell or shape named by `block`; equal positions mean a caret.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
33pub struct AwarenessCursor {
34    pub block: DocumentNodeId,
35    pub anchor: String,
36    pub head: String,
37}
38
39/// The visible extent of the participant's canvas, as the first and last
40/// visible block (a range of rows or one slide for sheets and decks).
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42pub struct AwarenessViewport {
43    pub first: DocumentNodeId,
44    pub last: DocumentNodeId,
45}
46
47/// What a client posts to `POST /documents/{id}/collab/awareness`.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
49pub struct AwarenessState {
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub session_id: Option<DocumentEditSessionId>,
52    pub color: String,
53    pub mode: AwarenessMode,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub cursor: Option<AwarenessCursor>,
56    #[serde(default, skip_serializing_if = "Vec::is_empty")]
57    pub selection: Vec<AwarenessCursor>,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub viewport: Option<AwarenessViewport>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
63pub struct DocumentAwarenessEvent {
64    pub document_id: DocumentId,
65    pub user_id: UserId,
66    pub display_name: String,
67    pub state: AwarenessState,
68    pub expires_at: DateTime<Utc>,
69}
70
71impl DocumentAwarenessEvent {
72    pub const NAME: &'static str = "document.awareness";
73}
74
75/// Why a live channel asks its client to re-join and reload the snapshot.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
77#[serde(rename_all = "snake_case")]
78pub enum LiveResyncReason {
79    SessionChanged,
80    UpdatesCompacted,
81    Lagged,
82}
83
84/// The first event of every live channel, and of every resumed one.
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
86pub struct DocumentLiveReadyEvent {
87    pub document_id: DocumentId,
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub edit_session_id: Option<DocumentEditSessionId>,
90    pub replayed: usize,
91}
92
93impl DocumentLiveReadyEvent {
94    pub const NAME: &'static str = "document.live.ready";
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
98pub struct DocumentLiveResyncEvent {
99    pub document_id: DocumentId,
100    pub reason: LiveResyncReason,
101}
102
103impl DocumentLiveResyncEvent {
104    pub const NAME: &'static str = "document.live.resync";
105}
106
107/// Sent before the server closes a channel whose caller lost view access.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
109pub struct DocumentLiveClosedEvent {
110    pub document_id: DocumentId,
111}
112
113impl DocumentLiveClosedEvent {
114    pub const NAME: &'static str = "document.live.closed";
115}