Skip to main content

waterui_preview_protocol/
hydrolysis.rs

1//! Offscreen Hydrolysis preview protocol.
2//!
3//! The CLI drives the generated Hydrolysis preview binary with a single JSON
4//! run configuration (passed as a file path through
5//! [`PREVIEW_RUN_CONFIG_ENV`]).
6
7use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10
11/// Environment variable carrying the path of the JSON-encoded
12/// [`PreviewRunConfig`] for the generated preview binary.
13pub const PREVIEW_RUN_CONFIG_ENV: &str = "WATERUI_HYDROLYSIS_PREVIEW_RUN_CONFIG";
14
15/// One offscreen Hydrolysis preview invocation.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct PreviewRunConfig {
18    /// Viewport width in logical units.
19    pub width: f32,
20    /// Viewport height in logical units.
21    pub height: f32,
22    /// What the run produces.
23    pub mode: PreviewRunMode,
24}
25
26/// What a preview run produces.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum PreviewRunMode {
29    /// A single PNG capture after the view tree has mounted.
30    Image {
31        /// Destination PNG path.
32        output: PathBuf,
33    },
34    /// A timeline capture: frames at `captures_ms` with `events` replayed at
35    /// their timestamps.
36    Scenario {
37        /// Directory receiving `frame-XXXXms.png` captures.
38        output_dir: PathBuf,
39        /// Capture timestamps in milliseconds from scenario start.
40        captures_ms: Vec<u64>,
41        /// Input events sorted by timestamp.
42        events: Vec<ScenarioEvent>,
43    },
44    /// Semantic accessibility-tree assertions (no render target).
45    Semantic,
46}
47
48/// One input event in a preview scenario timeline.
49#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
50pub struct ScenarioEvent {
51    /// Event timestamp in milliseconds from scenario start.
52    pub at_ms: u64,
53    /// Event kind.
54    pub kind: ScenarioEventKind,
55    /// Pointer x coordinate in logical units.
56    pub x: f32,
57    /// Pointer y coordinate in logical units.
58    pub y: f32,
59    /// Pointer button for down/up events.
60    pub button: ScenarioPointerButton,
61    /// Scroll delta along the x axis for scroll events.
62    pub dx: f32,
63    /// Scroll delta along the y axis for scroll events.
64    pub dy: f32,
65    /// Whether scroll delta values are line units instead of logical units.
66    pub is_line_delta: bool,
67}
68
69/// Event kind in a preview scenario.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71pub enum ScenarioEventKind {
72    /// Move the pointer without pressing.
73    PointerMove,
74    /// Press a pointer button.
75    PointerDown,
76    /// Release a pointer button.
77    PointerUp,
78    /// Cancel the active pointer.
79    PointerCancel,
80    /// Dispatch a wheel or trackpad scroll event.
81    Scroll,
82}
83
84/// Pointer button identifier for scenario events.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
86pub enum ScenarioPointerButton {
87    /// The primary button.
88    #[default]
89    Primary,
90    /// The secondary button.
91    Secondary,
92    /// The middle button.
93    Middle,
94}
95
96/// Environment variable carrying the path of the JSON-encoded
97/// [`McpRunConfig`] for the generated MCP binary.
98pub const MCP_RUN_CONFIG_ENV: &str = "WATERUI_HYDROLYSIS_MCP_RUN_CONFIG";
99
100/// One headless MCP session of a generated Hydrolysis binary.
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct McpRunConfig {
103    /// Viewport width in logical units.
104    pub width: u32,
105    /// Viewport height in logical units.
106    pub height: u32,
107    /// Display scale factor applied to the runtime.
108    pub scale_factor: f64,
109}
110
111#[cfg(test)]
112mod tests {
113    use super::McpRunConfig;
114
115    #[test]
116    fn mcp_run_config_round_trips() {
117        let config = McpRunConfig {
118            width: 390,
119            height: 844,
120            scale_factor: 2.0,
121        };
122        let json = serde_json::to_string(&config).expect("config serializes");
123        let parsed: McpRunConfig = serde_json::from_str(&json).expect("config deserializes");
124        assert_eq!(parsed, config);
125    }
126}