From 23e280a6b4da267b522d5d87d66b153c43325a82 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 22 Oct 2026 00:55:17 +0100 Subject: [PATCH] Separate library implementations from module declarations --- crates/ferese-activities/src/lib.rs | 626 +----- crates/ferese-activities/src/store.rs | 624 ++++++ crates/ferese-animation/src/lib.rs | 1171 +--------- .../ferese-animation/src/window_geometry.rs | 1166 ++++++++++ .../src/{ => window_geometry}/traces.rs | 0 crates/ferese-config/src/activities.rs | 29 +- crates/ferese-config/src/document.rs | 1171 ++++++++++ crates/ferese-config/src/lib.rs | 1155 +--------- crates/ferese-ipc/src/frame.rs | 206 ++ crates/ferese-ipc/src/lib.rs | 211 +- crates/ferese-layout/src/layout_tree.rs | 1929 ++++++++++++++++ crates/ferese-layout/src/lib.rs | 1933 +---------------- crates/ferese-layout/src/scrolling.rs | 29 +- crates/ferese-panel/src/panel.rs | 927 +------- crates/ferese-panel/src/panel/composition.rs | 923 ++++++++ crates/ferese-theme-model/src/lib.rs | 180 +- .../ferese-theme-model/src/resolved_theme.rs | 177 ++ docs/animation-model.md | 4 +- feresectl/src/activities/setup.rs | 2 +- 19 files changed, 6250 insertions(+), 6213 deletions(-) create mode 100644 crates/ferese-activities/src/store.rs create mode 100644 crates/ferese-animation/src/window_geometry.rs rename crates/ferese-animation/src/{ => window_geometry}/traces.rs (100%) create mode 100644 crates/ferese-config/src/document.rs create mode 100644 crates/ferese-ipc/src/frame.rs create mode 100644 crates/ferese-layout/src/layout_tree.rs create mode 100644 crates/ferese-panel/src/panel/composition.rs create mode 100644 crates/ferese-theme-model/src/resolved_theme.rs diff --git a/crates/ferese-activities/src/lib.rs b/crates/ferese-activities/src/lib.rs index 500e4e3..893cad2 100644 --- a/crates/ferese-activities/src/lib.rs +++ b/crates/ferese-activities/src/lib.rs @@ -1,631 +1,9 @@ //! Typed activity data and deterministic policy. The host supplies verified owners //! and suspend-aware milliseconds; this crate performs no I/O or rendering. pub mod pomodoro; +mod store; -use std::collections::BTreeMap; - -use serde::{Deserialize, Serialize}; - -pub type Time = u64; -const BOOST_MS: u64 = 5_000; -pub const MAX_TEXT: usize = 256; -pub const MAX_LEASE_MS: u64 = 300_000; - -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Owner { - pub pid: u32, - pub birth: u64, -} - -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Id { - pub owner: Owner, - pub local: String, - pub generation: u64, - pub session: u64, -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde( - tag = "kind", - content = "value", - rename_all = "kebab-case", - deny_unknown_fields -)] -pub enum Value { - Indeterminate, - Progress(f32), - Text(String), - Countdown { remaining_ms: u64, sampled_at: Time }, - PausedCountdown { remaining_ms: u64 }, -} - -impl Value { - pub fn remaining_ms(&self, now: Time) -> Option { - match self { - Self::Countdown { - remaining_ms, - sampled_at, - } => Some(remaining_ms.saturating_sub(now.saturating_sub(*sampled_at))), - Self::PausedCountdown { remaining_ms } => Some(*remaining_ms), - _ => None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde( - tag = "kind", - content = "data", - rename_all = "kebab-case", - deny_unknown_fields -)] -pub enum Template { - Progress, - Timer, - Media { artist: String, playing: bool }, - List { rows: Vec }, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum Source { - Publisher, - Command, - Media, - Capture, - Download, - Timer, - Notification, - Process, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum State { - Running, - Done, - Failed, - Cancelled, -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Data { - pub source: Source, - pub output: Option, - pub priority: u8, - pub icon: String, - pub title: String, - pub value: Value, - pub template: Template, -} - -impl Data { - pub fn validate(&mut self, now: Time) -> Result<(), &'static str> { - text(&self.title)?; - if self - .output - .as_ref() - .is_some_and(|output| text(output).is_err()) - { - return Err("invalid output name"); - } - if self.icon.len() > 64 - || !self - .icon - .bytes() - .all(|b| b.is_ascii_alphanumeric() || b == b'-') - { - return Err("icon must be a symbolic name"); - } - if !matches!( - self.icon.as_str(), - "hammer" - | "timer" - | "download" - | "media" - | "microphone" - | "camera" - | "call" - | "notification" - | "ferese" - ) { - self.icon = "ferese".into(); - } - match &mut self.value { - Value::Progress(progress) - if !progress.is_finite() || !(0.0..=1.0).contains(progress) => - { - return Err("progress must be finite and between zero and one"); - } - Value::Text(value) => text(value)?, - Value::PausedCountdown { remaining_ms } if *remaining_ms > 7 * 24 * 60 * 60 * 1_000 => { - return Err("countdown exceeds seven days"); - } - Value::Countdown { - remaining_ms, - sampled_at, - } => { - if *remaining_ms > 7 * 24 * 60 * 60 * 1_000 { - return Err("countdown exceeds seven days"); - } - *sampled_at = now; - } - _ => {} - } - match &self.template { - Template::Timer - if !matches!( - self.value, - Value::Countdown { .. } | Value::PausedCountdown { .. } - ) => - { - return Err("timer requires a countdown"); - } - Template::Progress - if matches!( - self.value, - Value::Countdown { .. } | Value::PausedCountdown { .. } - ) => - { - return Err("countdown requires timer template"); - } - Template::Media { artist, .. } => { - text(artist)?; - if !matches!( - self.value, - Value::Text(_) | Value::Indeterminate | Value::Progress(_) - ) { - return Err("invalid media value"); - } - } - Template::List { rows } => { - if rows.len() > 8 { - return Err("list exceeds eight rows"); - } - for row in rows { - text(row)?; - } - if !matches!(self.value, Value::Text(_) | Value::Indeterminate) { - return Err("invalid list value"); - } - } - _ => {} - } - Ok(()) - } -} - -fn text(value: &str) -> Result<(), &'static str> { - if value.len() > MAX_TEXT || value.chars().any(char::is_control) { - Err("text exceeds its budget or contains control characters") - } else { - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct Limits { - pub max_visible: usize, - pub show_badge: bool, - pub max_total: usize, - pub owner_ceiling: u8, - pub start_delay_ms: u64, - pub result_flash_ms: u64, -} - -impl Default for Limits { - fn default() -> Self { - Self { - max_visible: 2, - show_badge: true, - max_total: 128, - owner_ceiling: 160, - start_delay_ms: 300, - result_flash_ms: 1_500, - } - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Activity { - pub id: Id, - pub data: Data, - pub state: State, - pub created_at: Time, - pub eligible_at: Time, - pub refreshed_at: Time, - pub lease_deadline: Option