Skip to main content

termwright_protocol/
lib.rs

1//! Semantic side-channel client for the [termwright] terminal test driver.
2//!
3//! An instrumented TUI publishes its widget tree over a unix socket and
4//! commits each render with a signed OSC marker, so tests can assert on *roles
5//! and names* instead of screen-scraping cells. This crate is the protocol
6//! side of that contract: framing, the marker, message and snapshot
7//! validation, and a blocking socket client. It ships no framework adapter —
8//! wire it into whatever draws your screen.
9//!
10//! **Dormant rule.** Without `TERMWRIGHT_ENDPOINT` and `TERMWRIGHT_TOKEN` in
11//! the environment, [`Client::from_env`] returns `None` and nothing happens at
12//! all: no socket, no marker, no change to what the terminal receives.
13//!
14//! ```no_run
15//! use termwright_protocol::{Client, Node, Options, Rect, Role, Snapshot};
16//!
17//! let mut client = match Client::from_env(Options::new("my-tui", "1.0.0")) {
18//!     Some(client) => client,
19//!     None => return, // not instrumented: render normally and stop here
20//! };
21//! client.connect(termwright_protocol::DIAL_TIMEOUT).expect("handshake");
22//!
23//! let mut snapshot = Snapshot::new(80, 24);
24//! snapshot.push(Node::new("root", Role::Dialog, "Permission"));
25//! snapshot.push(
26//!     Node::new("ok", Role::Button, "Approve")
27//!         .with_parent("root")
28//!         .with_bounds(Rect::new(1, 2, 9, 1)),
29//! );
30//!
31//! if let Some(marker) = client.publish(&mut snapshot).expect("publish") {
32//!     // Only after the render's last byte has been written.
33//!     print!("{marker}");
34//! }
35//! ```
36//!
37//! The normative implementation is the TypeScript package
38//! `@termwright/protocol`; this crate is verified against the shared vectors
39//! in `clients/test-vectors`.
40//!
41//! [termwright]: https://github.com/gorce-ai/termwright
42
43#![forbid(unsafe_code)]
44#![warn(missing_docs)]
45
46pub mod client;
47pub mod debug;
48pub mod diffing;
49pub mod error;
50pub mod framing;
51pub mod limits;
52pub mod logs;
53pub mod marker;
54pub mod messages;
55pub mod roles;
56/// Bridge from `tracing`, enabled by the `tracing` feature.
57#[cfg(feature = "tracing")]
58pub mod tracing_layer;
59
60pub mod tree;
61pub mod validate;
62
63pub use client::{Client, Options, DIAL_TIMEOUT, ENV_ENDPOINT, ENV_PROTOCOL, ENV_TOKEN};
64pub use debug::{debug_path, Category, DebugLog, ENV_DEBUG, ENV_DEBUG_FILE};
65pub use diffing::{build_delta, diff_trees, DELTA_SHARE_CEILING};
66pub use error::{Error, ParseError, ValidationError, Violation};
67pub use framing::{encode_frame, project_dto, Frame, FrameDecoder, FRAME_HEADER_BYTES};
68pub use limits::{Limits, ABSOLUTE_LIMITS, DEFAULT_LIMITS, DEFAULT_NEGOTIATION_MS};
69pub use logs::{validate_log_record, AttrValue, LogLevel, LogRecord, LOG_LEVELS, MAX_LOG_ATTRS};
70pub use marker::{
71    compute_mac, encode_marker, verify_marker_payload, RenderMarker, MARKER_MAC_BYTES,
72    MARKER_OSC_CODE, MARKER_OSC_PREFIX,
73};
74pub use messages::{
75    parse_adapter_message, parse_driver_message, ProbeIdentityKind, ProbeInfo, PROTOCOL_ID,
76    PROTOCOL_V2_ID, PROTOCOL_VERSION,
77};
78pub use roles::{Action, Capability, Role};
79pub use tree::{
80    Cursor, CursorShape, Node, NodeGeometryObservations, Observation, Occlusion, Orientation,
81    PointerHitGrid, PointerHitRegion, Provenance, Rect, Snapshot, State, TextRange,
82};
83pub use validate::{apply_tree_delta, validate_snapshot, validate_tree_delta};
84
85/// The fields a node and a state may carry, as this client knows them.
86///
87/// Exposed so a test can compare them against the protocol's own exported
88/// lists: a field added upstream must fail a test here rather than wait to be
89/// noticed as a rejected snapshot in production.
90pub mod schema_keys {
91    pub use crate::validate::{NODE_KEYS, STATE_KEYS};
92}