Skip to content

Latest commit

 

History

History
179 lines (140 loc) · 6.43 KB

File metadata and controls

179 lines (140 loc) · 6.43 KB

Native App Shell

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.

Shell Contract

Use NativeRuntimeApp when Rust owns the app state:

  1. Build a UiFrame from state.
  2. Render the frame into a native host.
  3. Drain queued native events.
  4. Reduce action invocations into state.
  5. Rerender after state changes.
  6. 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.

Local Workflow

Run commands from the crates/gui directory.

just verify

just 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-ci

Use the headless dogfood session when changing protocol, reducer, or rendering logic:

just dogfood

Run the dogfood regression tests when changing app-shell event flow, menus, dialogs, keyboard routing, or close behavior:

just dogfood-regression

Use the native dogfood app when changing platform behavior:

just dogfood-native

The 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-winui

Before handing a native dogfood build to another developer, build and stage a host-native release artifact:

just release-native
just bundle-native

The staged artifacts and platform prerequisites are documented in packaging.md.

Dogfood Coverage

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 autoFocus candidates
  • 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.

Current Hardening Gaps

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