ONE TREE, MULTIPLE HOSTS

Share widgets across platforms.

Keep application components in terms of Widget, Scope, semantic events, and typed styles. Put DOM lookup, HTML generation, window creation, and event loops in small host-specific entry points.

Write the shared component once

This component contains no web-sys, HTML document, or window types. Its Cage and event handler have the same ownership and update behavior wherever the widget is mounted.

shared.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14
use glory::prelude::*; pub fn counter() -> impl Widget { widget(|scope| { let count = scope.cage(0_u64); col().class("counter").fill(( text().content(count.map(|count| format!("Count: {count}"))), button() .label("Increment") .on(activate, move |_| count.revise(|value| *value += 1)), )) .show_in(scope) }) }

Forward only the features you use

Glory has no default host. Application features forward to facade features, keeping server, browser, and native dependency graphs explicit. web and ssr each select html; hydrate selects web; stream selects ssr and resources; blitz is native-only. For a progressive browser client, stream-web selects stream and hydrate (and therefore web); hydrate the application after the final End frame.

Cargo.toml

1 2 3 4 5 6 7 8 9 10 11 12 13
[features] default = [] web = ["glory/web"] ssr = ["glory/ssr"] hydrate = ["glory/hydrate"] stream = ["glory/stream"] stream-web = ["glory/stream", "glory/hydrate"] blitz = ["glory/blitz"] desktop = ["glory/blitz-shell"] desktop-accessibility = ["glory/blitz-accesskit"] [dependencies] glory = { version = "0.3.1", default-features = false }

Choose the host entry point

web_and_server.rs

1 2 3 4 5 6 7 8 9 10
// Native server: produce a versioned fragment for an otherwise empty mount. let snapshot = glory::ssr::snapshot(counter())?; let fragment = snapshot.html; // wasm32 browser CSR: mount into an empty element and retain the WebApp. let app = glory::web::launch(csr_root, counter())?; // wasm32 browser hydration: adopt the matching SSR fragment instead. // Use the same initial widget tree, and retain this WebApp as well. let app = glory::web::hydrate(hydration_root, counter())?;

launch requires an empty, unclaimed browser mount. hydrate expects Glory's versioned SSR markers and validates the entire initial subtree before adopting it. The server has no browser input loop; interactivity begins with the browser host.

desktop.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] fn main() -> Result<(), Box<dyn std::error::Error>> { use glory::blitz::shell::{Shell, WindowApp, WindowAttributes}; let app = WindowApp::new(); app.mount(counter())?; let mut shell = Shell::new()?; shell.add_window( app, WindowAttributes::default().with_title("Glory counter"), ); shell.run()?; Ok(()) } #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] fn main() {}

WindowApp owns the shared App and native document. Shell owns the desktop event loop and windows. Enable blitz-shell for this entry point; blitz-accesskit additionally enables the optional desktop accessibility bridge.

Capability matrix

Headless · no feature

App::memory() records the semantic tree and typed patches for tests. Profile::RECORD performs no layout. There is no physical input, focus, geometry, or platform action driver.

Web CSR · web

wasm32 DOM rendering with browser input, focus, measurement, scrolling, resources, and RAF scheduling. launch creates a new tree; keep WebApp alive.

SSR · ssr

Native synchronous HTML strings, documents, and versioned snapshots through render, render_document, and snapshot. Event interests are recorded, but SSR has no physical input or geometry.

Hydration · hydrate

Browser adoption of a compatible SSR snapshot through hydrate or hydrate_with. It includes web, validates before changing the DOM, and preserves the mount on mismatch.

Streaming · stream

Native pull-driven Shell/Patch/End output with request resources and cancellation. Add web for the classic pre-edit script and hydrate for final application ownership; streaming alone is not a browser input host.

Blitz document · blitz

Native headless CSS/box/text layout, measurement, and programmatic scrolling. Optional paint/raster adds frames or CPU images. A standalone Renderer has no OS input driver.

Blitz desktop · blitz-shell

WindowApp and Shell add CPU desktop windows, activation, focus, navigation, controlled editing, and IME protocol integration on the desktop targets. AccessKit is a separate opt-in.

Sharing semantics does not erase platform differences

Cross-platform widgets do not guarantee pixel-identical output. Fonts, shaping, viewport/DPI behavior, CSS coverage, and native presentation differ. A green headless test proves neither layout nor displayed pixels, and neither headless memory nor a standalone Blitz Renderer supplies user input.

Keep current platform limits explicit

The desktop shell is gated to Windows, Linux, and macOS; it is not an Android host. Android Activity/Surface integration, mobile resources, input connections, and device acceptance remain separate work. Do not infer mobile support from an Android compile check or from desktop winit dependencies.

IME protocol handling is not proof that every operating-system IME works. Likewise, the optional AccessKit bridge and Windows UI Automation evidence are not screen-reader acceptance. Unix AT-SPI client checks, spoken announcements, complete text-selection interfaces, OS IME behavior, and mobile accessibility still require platform-specific validation.