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}