A3S GUI apps are reducer-driven native shells. The platform owns windows, menus, focus, and input events; application code owns state and frame construction. A shell is production-ready when those two halves can be started, driven, tested, and stopped through the same lifecycle on each native backend.
Use NativeRuntimeApp when Rust owns the app state:
- Build a
UiFramefrom state. - Render the frame into a native host.
- Drain queued native events.
- Reduce action invocations into state.
- Rerender after state changes.
- Stop the loop when the state predicate returns false.
AppKitRuntimeApp, WinUiRuntimeApp, and Gtk4RuntimeApp are type aliases for
that same contract with platform-native hosts. Their run_*_while methods render
the first frame if needed, pump platform events, drain queued A3S events, and
stop when either the root window closes or the state predicate returns false.
For embedded hosts and automation, handle_pending_native_event_batch_while
returns the same per-event responses plus batch diagnostics for how many native
events were drained, handled, and left buffered when the predicate stopped. The
platform event-pump helpers expose the same diagnostics through
pump_appkit_event_batch_while, pump_gtk4_event_batch_while, and
pump_winui_event_batch_while.
Window close should be modeled as state:
use a3s_gui::{rsx, ComponentCx, RSX, WindowOptions};
fn editor(cx: &mut ComponentCx<EditorState>) -> RSX {
let close_editor = cx.use_reducer("closeEditor", |state: &mut EditorState, _action| {
state.close_requested = true;
Ok(())
});
rsx!(<Button key="close" onPress={close_editor}>Close</Button>)
}
let editor = ComponentCx::compile("editor", editor)?.with_window(WindowOptions {
title: "Editor".to_string(),
on_close: Some("closeEditor".to_string()),
width: None,
height: None,
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: true,
});The reducer should set an exit flag. The platform loop should observe that flag:
app.run_appkit_while(|state| !state.close_requested)?;This keeps menu close, root-window close, and explicit quit actions on the same path.
Run commands from the crates/gui directory.
just verifyjust verify runs formatting, Rust tests, example tests, platform planning
tests, clippy correctness/suspicious checks, rustdoc with warnings denied, and
whitespace checks. The crate pins Rust 1.95.0 through rust-toolchain.toml, and
Cargo-based verification uses Cargo.lock via --locked.
The repository CI runs just verify on Linux, then runs host-native AppKit,
GTK4, and WinUI library tests plus all-target checks with the corresponding
native feature on the matching operating systems. Headless example tests cover
the shared dogfood regression once instead of repeating it in every native job.
Pushes to main and manual workflow runs also build, stage, validate, and upload
compressed unsigned native dogfood bundles for manual platform QA.
Run the same native compile-and-test gate on the current host with:
just native-ciUse the headless dogfood session when changing protocol, reducer, or rendering logic:
just dogfoodRun the dogfood regression tests when changing app-shell event flow, menus, dialogs, keyboard routing, or close behavior:
just dogfood-regressionUse the native dogfood app when changing platform behavior:
just dogfood-nativeThe native recipe selects the matching backend for the current operating system:
| Host | Example |
|---|---|
| macOS | appkit_dogfood with appkit-native |
| Linux | gtk4_dogfood with gtk4-native |
| Windows | winui_dogfood with winui-native |
For cross-target Windows checks from a configured non-Windows host:
just check-winuiBefore handing a native dogfood build to another developer, build and stage a host-native release artifact:
just release-native
just bundle-nativeThe staged artifacts and platform prerequisites are documented in
packaging.md.
The shared dogfood app exercises the shell features that need to keep working before A3S GUI is considered generally usable:
- root window metadata, size hints, resize bounds, and
window.onClose - menus and menu item actions
- text input and textarea length/sizing hints, initial/rerendered and change-event max-length clamping, focus/blur routing, select, slider range bounds and step hints, tabs, switches, and checkboxes
- native focus ownership across conditional field removal and later
autoFocuscandidates - overflow-aware root containers that become native scroll viewports
- reducer-driven rerendering after native events
- stale queued native events after reducer-driven node removal
- dialog open state plus native dialog close event routing
- keyboard down, key release, and review shortcut routing
- review gates that disable completion until state is valid and suppress disabled completion actions
- state-driven app loop exit after close actions
Headless dogfood tests cover both embedded NativeRuntimeApp handling and the
host/process boundary exposed by NativeProtocolApp. Native checks ensure the
matching backend compiles and can host the dogfood surface.
The app shell is usable for dogfood and smoke applications. Before treating it as
a stable production surface, keep hardening these areas. Scheduled delivery and
acceptance gates are tracked in roadmap.md:
- signed installers and automated app package generation for each product
- broader resize, focus, and text input edge cases under longer real-world forms
- native-platform automation for dogfood menu, dialog, and keyboard interaction flows beyond compile-time checks
- direct OS-native wake messages for externally started work while an otherwise idle loop is inside the platform's blocking wait; pending work already uses a joinable effect waker plus a bounded 16 ms park fallback
- interruptible SDK calls for fast shutdown; the default effect scope now joins non-cooperative work, which prevents lifetime escape but may wait for an already-running SDK call to return
- native failure-injection proving that executor teardown closes every partial AppKit, GTK4, and WinUI surface before full replay
- Windows smoke automation for the isolated WinUI programmatic-focus ABI bridge