//! Persisted custom chat draft model. use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::ids::{LayoutId, NodeId, ProjectId}; /// Maximum draft text size persisted by IdeA. pub const CHAT_DRAFT_MAX_BYTES: usize = 100 * 1024; /// Stable scope for one custom chat draft. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChatDraftKey { /// Project owning the layout. pub project_id: ProjectId, /// Named layout containing the chat cell. pub layout_id: LayoutId, /// Layout node hosting the chat cell. pub node_id: NodeId, } impl ChatDraftKey { /// Builds a draft key. #[must_use] pub const fn new(project_id: ProjectId, layout_id: LayoutId, node_id: NodeId) -> Self { Self { project_id, layout_id, node_id, } } } /// Durable text draft for one custom chat composer. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChatDraft { /// Stable draft scope. pub key: ChatDraftKey, /// Draft text. Attachments are deliberately excluded. pub text: String, /// Last update time, epoch milliseconds. pub updated_at_ms: u64, } impl ChatDraft { /// Builds validated draft metadata. /// /// # Errors /// Returns [`ChatDraftError::TooLarge`] when `text` exceeds the maximum /// persisted draft size. pub fn new( key: ChatDraftKey, text: impl Into, updated_at_ms: u64, ) -> Result { let text = text.into(); if text.len() > CHAT_DRAFT_MAX_BYTES { return Err(ChatDraftError::TooLarge { max: CHAT_DRAFT_MAX_BYTES, actual: text.len(), }); } Ok(Self { key, text, updated_at_ms, }) } } /// Chat draft validation errors. #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum ChatDraftError { /// Draft content exceeds the persisted size limit. #[error("chat draft exceeds {max} bytes: {actual}")] TooLarge { /// Maximum allowed bytes. max: usize, /// Actual text bytes. actual: usize, }, }