a3s-gui accepts structured UI input and emits native platform commands for
AppKit, WinUI, and GTK4 backends.
The input contract is structured RSX element data: protocol element records,
semantic element names, className, inline style objects, aria-*, data-*,
and DOM-style event props. Supported semantic_ui component names are treated
as semantic identifiers. The renderer consumes A3S Native UI IR; host adapters
create platform widgets directly.
Rust ComponentCx function + optional RSX view template
|
v
parse_rsx / RsxComponent
|
v
CompiledRsxNode records
|
v
UiFrame protocol
|
v
RsxCompilerBridge
|
v
Semantic UI tree
|
v
A3S Native UI IR
|
v
Keyed renderer diff engine
|
v
Native command stream
|
v
NativeHost adapter
- AppKit on macOS
- WinUI on Windows
- GTK4 on Linux
|
v
Native events
|
v
HostEvent protocol
|
v
InteractionState
|
v
EventRouter
|
v
Action ids
The bridge accepts semantic component names, HTML and SVG intrinsic names,
common Web props, and event names. The native renderer receives a typed,
portable protocol. Input records come from Rust ComponentCx functions,
registered .rsx view templates, RsxComponent hook registrations, or Rust
code that builds the same protocol shape.
Allowed data:
- semantic component names, HTML intrinsic element names, and SVG intrinsic element names
- labels, values, placeholders, orientation, disabled state, selected state, checked state, expanded state, read-only state, multiple-selection state, autofocus state, text-entry hints, text-length hints, textarea sizing hints, and ranged values
- list, dialog, popover, tab, menu, and form structure
- stable keys for reconciliation
className, Tailwind utility classes, inlinestyleobjects, and CSS text style strings as portable style inputaria-*,data-*, and HTML attributes as metadata- common RSX event prop names such as
onClick,onChange,onInput,onFocusChange,onExpandedChange,onKeyDown, andonKeyUpwith named callback functions, normalized into native actions
Non-portable runtime assumptions:
- direct DOM access, for example
document.querySelector - measuring browser layout APIs as the source of truth
- relying on arbitrary CSS selectors that cannot be expressed as native style tokens
- treating an
HTMLElementinstance as an application object
Built-in rsx_ui definitions are compiled once per process. The default
registry is a LazyLock<GuiResult<ComponentRegistry>>; ordinary
RsxComponent and ComponentCx constructors receive a cheap clone after the
first initialization instead of recompiling every built-in template for every
page. ComponentRegistry stores templates, contracts, and class variants in
separate immutable Arc maps. Clones share those maps, and registering an
application-specific definition uses Arc::make_mut to detach only the map
being changed. The built-in registry builder itself uses bare compilation, so
initialization cannot recursively request the default registry.
The constructor family makes registry ownership explicit:
- With
design-systemenabled,RsxComponent::new,from_source,from_file, andfrom_template, andComponentCx::compile, install the shared default registry. Anauthoring-only build uses an empty default and therefore has no hiddenrsx_uidependency. - The corresponding
*_bareAPIs, includingComponentCx::compile_bare, do not install built-inrsx_uidefinitions. - The
*_with_registryAPIs andComponentCx::compile_with_registryaccept an explicitly assembledComponentRegistry.builtin_component_registry()is available when an application wants to extend the shared defaults once and reuse the resulting copy-on-write registry across its pages.
This is a runtime ownership boundary, not a second component model. All three paths produce the same compiled RSX tree and enter the same semantic/native lowering pipeline.
Cargo features enforce the authoring/runtime split inside the current crate.
authoring enables SWC-backed RSX parsing and ComponentCx; design-system
depends on authoring and enables the built-in rsx_ui registry. The default
feature set keeps the existing authoring experience, while
cargo check --no-default-features --lib proves that the runtime, protocol,
semantic mapper, renderer, and planning core compile without SWC or rsx_ui.
The remaining dependencies stay one-way:
ComponentCx, RSX parsing, andrsx_uiauthoring compile outward-facing syntax intoCompiledRsxNode; native backends never execute component functions.rsx_uidepends on semantic component contracts. The semantic mapper, native IR, renderer, and platform planning layers do not depend on the built-in design-system registry.src/native.rs, reconciliation, andsrc/platform/remain independent of OS widget handles. AppKit, GTK4, and WinUI surfaces depend on those portable types and are selected behind their target/feature boundaries.src/backend/executes planned commands but does not own product state or product I/O. The serializable host boundary carries data and command records, never component runtime instances or thread-affine native handles.src/effect.rsdefines an executor seam without depending on Tokio or another application runtime. An application may inject such an executor at the outer edge.
Product applications depend on these layers; the GUI core must not import
product modules, storage clients, network clients, or process runners.
New product configuration and capability policy use ACL (.acl) exclusively.
The application or host owns ACL parsing and validation, then passes typed,
immutable capability grants across the outer boundary. The GUI core owns the
capability types but must not depend on the ACL parser or its AST.
NativeCapabilities describes the behavior a backend claims for each role.
NativeInputConformanceManifestV1 turns native press claims into a canonical
role/scenario matrix, including activation, cancellation, disabled-state, and
keyed-rerender cases for each applicable input modality. This derivation keeps
capability declarations and test scope in one source of truth.
An operating-system runner records semantic events with
NativeInputConformanceObservationV1::capture and submits a versioned
NativeInputConformanceRunV1. Capture excludes event values and raw key data.
The verifier compares observations against the generated manifest, checks
provenance and environment identity, and produces a serializable
NativeInputConformanceReportV1. Headless and adapter-kernel traces are
explicitly ineligible to prove a native claim even when their event sequence is
identical.
File I/O remains outside the GUI core. The
a3s-gui-native-input-conformance binary is a thin adapter that prints current
manifests and reads evidence JSON for CI. Native backends own the OS automation
driver and event-loop integration; the portable verifier owns only the shared
semantic contract.
The Windows-only a3s-gui-winui-input-smoke harness keeps automation outside
the runtime library. It mounts button-backed semantic roles as real WinUI
Buttons and collection items inside real XAML list containers, confirms that
the mounted native role matches the manifest case, and locates and verifies its
enabled state through the OS UI Automation tree. Mouse and Enter-key input use
SendInput; pen and touch use CreateSyntheticPointerDevice /
InjectSyntheticPointerInput; assistive activation uses InvokePattern for
buttons and SelectionItemPattern for collection items. Mouse cancellation
releases the real XAML pointer capture after the injected press; pen and touch
cancellation inject Windows' cancelled-up pointer state. The production WinUI
message loop records successful, cancelled, keyed-rerender, and disabled
responses. The strict manifest verifier validates all 98 observations for
Button, DisclosureSummary, Link, ImageMapArea, MenuItem,
ListBoxItem, and TreeItem, so the output is a complete current WinUI run
artifact. The harness requires an interactive Windows desktop and the Windows
App Runtime 1.7 framework package used by the WinUI backend's dynamic
dependency.
Every platform adapter implements the same host operations:
- create a native widget for a
NativeElement - update native properties without remounting
- insert a native child at a stable index
- remove a native subtree
- set the root widget for a window or surface
The renderer owns reconciliation. Platform adapters own native object lifetime,
thread affinity, focus integration, accessibility integration, and event delivery.
The runtime does not require native hosts, command executors, event sources, or
widget drivers to be Send: real GUI handles are often confined to the main UI
thread.
Hosts model a tree, not a graph. Inserting an existing child reparents it from
any previous parent, and shared host implementations reject self-parenting or
ancestor cycles before recording a command.
The pure Rust PlatformPlanningHost implements the same NativeHost contract
without linking platform SDKs. It is an executable specification for native
adapters: given a NativeElement, it records the AppKit, WinUI, or GTK widget
class, accessibility role, action binding, style tokens, events, and metadata
that a platform backend applies. The adapter blueprint boundary applies the same
native prop normalization used by the renderer, so direct planning calls and
rendered command streams share value clamping, step snapping, and invalid
numeric-value omission semantics.
The compiler bridge accepts the HTML element registry exposed by HTML_ELEMENTS.
Each recognized intrinsic tag lowers to the closest native semantic role, and
the original tag is preserved as data-a3s-html-tag metadata. Document,
metadata, template, slot, text, text-annotation, phrasing-text, flow-text,
legacy-text, legacy-frame, fallback-content, math, selected-content, heading,
heading-group, ruby annotation, landmark, sectioning, disclosure, figure,
description-list, form, form-grouping, option-group, output, meter, list,
dialog, menu, media, embedded-content, link, image-map, and table-structure
tags lower to dedicated native roles. input[type=range] lowers to a native
slider role, numeric value or defaultValue props and attributes are
projected as the ranged current value, and numeric step is projected as
native ranged-control step state. input[type=number] lowers to the native
text-field role while numeric value or defaultValue, min, max, and
step props and attributes are preserved as native control state; the text
value is also retained for text-field backends. GTK planning maps that
number-shaped text field to gtk::SpinButton.
input[type=button], input[type=submit],
input[type=reset], and input[type=image] lower to native button roles with
HTML fallback labels from value, default submit/reset labels, or image alt
text. input[type=hidden] keeps its form name, form, type, and value
state in the native IR but is marked hidden so platform widget config,
rendered accessibility projection, and native event routing treat it as
invisible. Value-bearing input and textarea value or defaultValue
attributes are projected as initial native values when no top-level protocol
value is supplied; button-like input controls keep using value as their
fallback label instead. HTML dialog open state projects into a structured
HtmlDialogProps value and controls native widget visibility for intrinsic
dialog elements through the same derived visible config used by the platform
surfaces; HtmlDialogProps remains the protocol metadata for replay and
authoring semantics. input and textarea placeholder attributes, plus
aria-placeholder, project into native placeholder state. textarea direct
text children project into native text-field value state when no explicit value
is supplied. HTML
form-control attributes including readonly/readOnly, multiple,
autofocus/autoFocus, autocomplete/autoComplete,
inputmode/inputMode, enterkeyhint/enterKeyHint,
autocapitalize/autoCapitalize, autocorrect/autoCorrect,
virtualkeyboardpolicy/virtualKeyboardPolicy, pattern,
minlength/minLength, maxlength/maxLength, rows, cols, and size
project into native control-state fields and stay available in metadata.
rows, cols, and size only affect native sizing when they are positive
integers, so zero-valued form sizing attributes remain metadata without
shrinking native controls.
Text-field values are clamped to maxLength before initial render, before
rerender updates, and when native change events arrive.
ARIA relationship attributes including aria-labelledby, aria-describedby,
aria-details, aria-controls, aria-owns, aria-flowto,
aria-errormessage, and aria-activedescendant project into structured
native accessibility relationship hints and stay available in metadata. A
shared native registry resolves their ID-reference lists against mounted node
id metadata, including forward references, id changes, removal, and
duplicate-id ambiguity. GTK4 projects all eight relationships through
GtkAccessible. WinUI projects described-by, controls, and flow-to lists plus
a single labelled-by target through UI Automation. AppKit projects a single
static-text labelled-by target as its title UI element. Remaining
backend/field combinations and non-accessible wrappers retain portable
semantics and report field-level capability issues.
ARIA description and value attributes including aria-description,
aria-roledescription, aria-keyshortcuts, and aria-valuetext project into
structured native accessibility description hints and stay available in metadata.
ARIA structural attributes including aria-level, aria-posinset,
aria-setsize, aria-rowcount, aria-rowindex, aria-rowspan,
aria-colcount, aria-colindex, aria-colspan, aria-rowindextext, and
aria-colindextext, plus aria-sort, project into structured native
accessibility structure hints and stay available in metadata. Values are
normalized through one shared registry before platform projection: levels,
positions, indices, and spans must be positive; known counts bound their
corresponding positions and ranges; unknown set/grid sizes retain the ARIA
-1 sentinel; and sort accepts only none, ascending, descending, or
other. GTK4 projects all twelve fields through exact GtkAccessible
properties and relations. WinUI projects level, position-in-set, and
size-of-set through AutomationProperties. AppKit applies conservative
disclosure-level, row/column count, index-range, and sort-direction hints, but
the fields remain reported as portable because the generic AppKit APIs do not
preserve the complete ARIA contract. GTK4 menu-model items and the WinUI window
wrapper retain the structure in portable output because neither exposes the
required generic native accessibility target.
ARIA state and live-region attributes including aria-hidden,
aria-autocomplete, aria-multiline, aria-current, aria-haspopup,
aria-pressed, aria-live, aria-atomic, aria-busy, aria-relevant, and
aria-modal project into structured native accessibility state hints and stay
available in metadata. State capabilities are reported field by field rather
than as one aggregate claim. AppKit projects hidden and modal state through
NSAccessibility. GTK4 projects hidden, autocomplete, multiline, popup
presence, pressed, busy, and modal through GtkAccessible; popup subtype and
aria-current remain portable. Generic WinUI direct-state gaps remain
portable, while ContentDialog supplies native modal semantics. Invalid
autocomplete, current, popup, and pressed tokens fail accessibility
conformance. aria-hidden is accessibility-only state and does not change
native widget visibility, but aria-hidden="true" omits the subtree from
rendered accessibility tree projection.
After the initial render, mounted live regions are diffed in stable tree order.
aria-relevant selects additions, removals, and text changes;
aria-atomic="true" emits the complete current accessible text; and
aria-busy="true" on the region or any ancestor defers updates until the busy
state clears. The WAI-ARIA defaults for alert, status, log, timer,
marquee, and HTML output are honored. Initial status content is quiet,
while an initial alert is announced. A nested live boundary owns its subtree so
different politeness levels cannot produce duplicate parent and child messages.
Hidden, inert, and sensitive-value content is excluded.
HTML global attributes including title, hidden, lang, dir,
tabindex/tabIndex, role, accesskey/accessKey,
contenteditable/contentEditable, draggable, spellcheck/spellCheck,
translate, inert, popover, anchor, is, and global nonce project
into native fields and stay available in metadata. Native surfaces map title
to platform tooltip/title hints where available. hidden also makes the native
widget config invisible. Shadow DOM distribution and style-part attributes such
as slot, part, and exportparts/exportParts project into a structured
HtmlShadowProps value for native composition and styling adapters. Microdata
attributes such as itemscope/itemScope, itemprop/itemProp,
itemtype/itemType, itemid/itemID, and itemref/itemRef project into
a structured HtmlMicrodataProps value for native metadata adapters. Form submission attributes and overrides project into
native fields for matching form and submit-control tags. Media and resource
attributes including href, src, srcset/srcSet,
sizes, media, resource type, intrinsic width/height, loading,
decoding, fetchpriority/fetchPriority, crossorigin/crossOrigin,
referrerpolicy/referrerPolicy, poster, controls, autoplay/
autoPlay, loop, muted, playsinline/playsInline, preload, kind,
srclang/srcLang, label, and default project into native fields for
matching image, media, source, track, link, script, embed, iframe, object, and
related resource tags. Resource policy attributes including anchor and area
target, download, ping, rel, hreflang/hrefLang, link as,
integrity, blocking, nonce, imagesrcset/imageSrcSet,
imagesizes/imageSizes, link disabled, script async, defer,
nomodule/noModule, iframe name, allow,
allowfullscreen/allowFullScreen, sandbox, and srcdoc/srcDoc project
into a structured HtmlResourcePolicyProps value for native resource loading,
navigation, and embedding adapters. Form association and range metadata such as
label for/htmlFor, output for/htmlFor, and meter low, high, and
optimum project into a structured HtmlFormAssociationProps value for native
accessibility and range adapters. Activation attributes such as button
command, commandfor/commandFor, popovertarget/popoverTarget, and
popovertargetaction/popoverTargetAction, plus popover target attributes on
button-like input controls, project into a structured HtmlActivationProps
value for native command, popover, and action adapters. Text annotation
attributes such as quote and change cite, change datetime/dateTime, and
time datetime/dateTime project into a structured
HtmlTextAnnotationProps value for native citation, provenance, and temporal
adapters. HTML option and data value
attributes project into native value state. Table and list structure attributes including
colspan/colSpan, rowspan/rowSpan, headers, scope, abbr, span,
start, reversed, list type, and list item value project into native
fields for table cells, columns, column groups, ordered lists, and list items.
The HTML prop lowering implementation is split under src/compiler/props/ into
shared attribute parsing, control/form aliases, global attributes,
resource/media aliases, activation aliases, text annotation aliases, dialog
aliases, shadow/style-part aliases, microdata aliases, resource policy aliases,
semantic state aliases, form association aliases, table/list structure aliases,
and tag-specific native state rules. Shared activation, text annotation,
dialog, shadow/style-part, microdata, resource policy, form association, and
table/list hints are represented by HtmlActivationProps,
HtmlTextAnnotationProps, HtmlDialogProps, HtmlShadowProps,
HtmlMicrodataProps, HtmlResourcePolicyProps, HtmlFormAssociationProps,
and HtmlCollectionProps under src/html/; native and semantic UI builder
methods for those grouped hints are split into dedicated submodules.
Generic HTML containers lower to NativeRole::View; unsupported custom elements
with a hyphenated tag name also lower to a generic native view.
The SVG element registry exposed by SVG_ELEMENTS follows the same lowering
path for vector and icon RSX trees. Recognized SVG tags lower to generic native
views or text nodes and preserve the original tag as data-a3s-svg-tag
metadata.
GuiRuntime is the public orchestration API. It accepts compiled RSX trees,
supported semantic component trees, or native IR trees and renders them into any
NativeHost. InteractionState updates platform-independent focus, press,
long-press, movement and its latest incremental delta, value, checked,
selected, and expanded state from native events before action routing.
EventRouter maps native events such as press, long-press, move lifecycle,
collection action, change, focus, toggle, selection change, key down, and key
up back to serialized action identifiers such as onClick, onLongPress,
onMoveStart, onMove, onMoveEnd, onChange, onInput, onFocusChange,
onAction, onExpandedChange, onKeyDown, and onKeyUp.
Focus and blur events always carry canonical true and false payloads for
onFocusChange. These direct focus callbacks are target-only. The runtime
separately derives onFocusWithin, onBlurWithin, and
onFocusWithinChange from the focused target and its relatedTarget, routing
them only across entered or exited subtree boundaries. Consecutive native
blur/focus events are linked before dispatch so focus movement between sibling
descendants preserves their ancestor's focus-within state. Toggle events for
checked or expanded controls canonicalize
native boolean strings such as 1, 0, on, and off; invalid toggle
payloads fall back to the next state derived from the current rendered or
interactive state. Boolean change events for checked controls use that same
canonical payload contract before action dispatch.
When a key-down event has no explicit onKeyDown binding on the target or its
route ancestors, Enter and Space fall back to the primary press action for
activatable controls such as buttons, links, and menu items. For stateful
controls without an explicit key-down handler on that route, keyboard
activation is normalized before routing: Space toggles checkboxes and switches,
Space selects radios, and Enter or Space toggles expanded controls and selects
listbox items or tabs.
Before that activation fallback, the mounted selection registry resolves
collection navigation for ListBox, Menu, Tree, Tabs/TabList, and RadioGroup.
Arrow keys follow the collection orientation and inherited text direction;
Home and End move to the first and last focusable item. PageUp and PageDown
request a fresh CollectionLayoutSnapshot from the host and move by one
visible width or height using stable-key item rectangles, including
variable-size rows. AppKit reads the list document and row frames, GTK4 reads
list allocations and ancestor scroll adjustments, and WinUI reads actual
list/item layout. Custom or headless hosts can install the same typed snapshot
through GuiRuntime::set_collection_layout; missing measurement falls back to
the corresponding collection boundary. Fully disabled items are skipped, items
disabled only for selection remain focusable, and shouldFocusWrap controls
end-to-start navigation except for Tabs and RadioGroup, which wrap by default.
Focus movement is issued through the same constrained RequestFocus capability
as imperative navigation.
Replace-selection ListBox and Tree collections select as focus moves, toggle
collections move focus only, radio navigation selects, and Menu navigation is
focus-only. Tabs select on focus unless keyboardActivation="manual" is set.
Shift extends multiple selection; Control or Command moves focus without
selection. Multiple ListBox and Tree collections map Control/Command+A to the
explicit all selection and Escape to an empty selection. Escape defaults to
that clear behavior, can be released with escapeKeyBehavior="none", and still
honors disallowEmptySelection. These commands route through the collection
root as complete stable-key snapshots, including normal action failure rollback.
Explicit onKeyDown bindings on the target route take precedence over all
built-in collection handling.
Collection onAction is a separate semantic channel from selection. The
mounted registry projects an internal action marker onto owned ListBoxItem and
TreeItem rows so native adapters capture a press lifecycle without copying the
root callback onto each item. The runtime then bubbles Action from the item
to the collection root with the stable item key. Enter invokes action while
Space selects. Mouse replace behavior selects on the first click and invokes
action on the second; touch, pen, and virtual activation invoke action directly.
For touch and pen, holding through the long-press threshold selects the item
and enters a persistent touch-selection mode. Subsequent taps select instead
of acting until the selection becomes empty. The shared recognizer uses an
AppKit NSTimer, GTK main-loop timeout, or WinUI
DispatcherQueueTimer to deliver the terminal event at the threshold. Release
also evaluates elapsed time as a fallback if a platform timer cannot be
scheduled. Threshold recognition first ends long-press-start, then cancels the
active press and any active move lifecycle before routing the terminal
long-press event, so a later release cannot also activate the item.
When an action gesture also causes a native collection to select a row, a
single-use suppression token restores the mounted stable-key snapshot before
any selection callback is routed. disabledBehavior="selection" therefore
blocks selection while retaining action, whereas a fully disabled item blocks
both.
Tree adds a portable hierarchy over that collection kernel. Semantic nested
TreeItem nodes are flattened in preorder for native mounting, while stable
parent keys and accessibility level/position/set-size metadata preserve their
logical structure. Controlled expandedKeys is reapplied on every render;
defaultExpandedKeys seeds state once and then survives keyed rerenders.
Collapsed descendants are projected as hidden host rows and are excluded from
arrow navigation, boundaries, typeahead, focus candidates, and accessibility
projection. In LTR, Right expands a collapsed parent or enters its first visible
child, and Left collapses an expanded parent or focuses its parent. RTL mirrors
those keys. Expansion emits the complete key set through the Tree root's
onExpandedChange; host projection and mounted state roll back together if the
action fails. If controlled state hides the focused row during a rerender, focus
moves to the nearest visible ancestor.
Printable keys on ListBox, Menu, Tree, Select, and ComboBox items enter a
500 ms typeahead buffer owned by the mounted collection. Search uses explicit
textValue first, then the accessible label and value, and begins at the
current item before wrapping. ICU4X applies the inherited BCP 47 locale with
search collation at primary strength, matching React Aria's case- and
accent-insensitive behavior without reducing international text to ASCII.
The buffer is retained with stable collection identity across keyed rerenders.
Control and Command shortcuts do not enter it; fully disabled items are
skipped, while items disabled only for selection can still receive focus but
do not emit a selection action. AppKit reads the produced character, GTK4
prefers the GDK Unicode scalar over its symbolic key name, and WinUI uses
ToUnicode with the active keyboard layout and the non-mutating flag before
falling back to a stable virtual-key name. Full IME and dead-key composition
remains an adapter conformance gap.
I18nManager also resolves that inherited locale for reusable
LocaleCollator, LocaleNumberParser, LocaleNumberFormatter,
LocaleDateFormatter, and LocaleMessageFormatter objects.
The public collator exposes search/sort usage, ECMA-402 sensitivity,
case-first, numeric ordering, and locale-equivalent starts-with, ends-with, and
contains filters. Decimal parsing exposes complete parsing, partial-input
validation with sign bounds, and numbering-system detection. It automatically
detects Latin, Arabic, Han decimal, Devanagari, Bengali, and full-width digits
unless a BCP 47 -u-nu- extension selects one system explicitly. Decimal
and percent formatting exposes grouping, sign-display, bounded fraction digits,
half-expand rounding, locale-specific percent patterns, and model/display
scaling. Percent parsing returns model-space values, so 45% becomes 0.45.
Date and time formatting exposes short through full styles, optional seconds,
calendar selection, numbering systems, and hour-cycle overrides through BCP 47
Unicode extensions. These utilities use compiled ICU4X data, are Send + Sync,
and perform no platform, file, or network I/O. The collection typeahead path
uses the same public collator filter. Native number text fields use the cached
parser and formatter for localized display, range/step normalization, and
model-space change events, so interactive behavior and application-level
locale rules cannot drift.
LocaleMessageFormatter owns the small accessibility-message layer that must
follow inherited locale even though no Web runtime exists. Its NumberField
catalog contains the same decrease, increase, numeric-role, and empty-value
messages for 34 React Aria locales. Language fallback is deterministic, with
separate Portuguese regions and simplified/traditional Chinese resolution.
NumberField is represented as a native group with separate decrement button,
numeric text input, and increment button nodes. The semantic hook computes
button action values and canIncrement/canDecrement from the same
minimum-anchored step grid used by runtime ArrowUp/ArrowDown/PageUp/PageDown
handling; Home/End target the declared bounds. Modified key combinations are
not claimed. A signed Wheel event carries context.delta; AppKit and WinUI
normalize their native upward-positive values to the DOM-style sign used by
GTK4 and the portable runtime. NumberField consumes only focused,
vertical-dominant, non-control wheels unless isWheelDisabled is set. The
shared interaction profile marks handled keys and wheels on AppKit, GTK4, and
WinUI so a toolkit-native numeric control cannot apply a second change. A
mouse stepper PressStart resolves the marked sibling input and requests
native focus after the platform press, while touch and virtual focus remain on
their native target. Marked stepper buttons share a cancellable hold state
machine across AppKit, GTK4, and WinUI. Mouse and pen presses step immediately,
start repeating after 400 milliseconds, and repeat every 60 milliseconds.
Touch defers its first repeat for 600 milliseconds and emits a short tap only
on release. Leaving the button, pointer cancellation, disabled/read-only state,
or a step boundary stops the active timer; re-entry starts a fresh cycle.
Native terminal button activation is consumed so it cannot duplicate the
portable step. Read-only and disabled fields retain focus semantics but do not
produce step changes.
Automatic stepper labels and the numeric-input role description are projected
after locale inheritance, so custom labels remain untouched while native
buttons receive localized defaults. A focused marked numeric input compares
its previous and current normalized accessible value after reconciliation.
Changes emit an assertive AccessibilityAnnouncement; empty values use the
effective locale's message and ASCII minus signs are normalized to U+2212.
AccessibilityAnnouncementHost is an optional host capability. The planning
host serializes it, headless records it, and the native command stack forwards
it to AppKit accessibility notifications, GTK4 accessible announcements, or
WinUI UI Automation notifications.
The same channel carries general live-region changes. Its portable diff policy
normalizes whitespace, emits non-atomic fragments in mounted order, coalesces
busy updates into one complete message, and maps polite/assertive to the
platform priority. Capability audits therefore request the native announcement
feature for explicit or implicit live regions as well as NumberField.
AppKit keeps logical item identity separate from the collection selection
target: a row button is registered as the ListBoxItem or TreeItem responder,
while activation reports the selected value to the owning collection.
Programmatic item focus resolves that logical id to the current concrete row,
and row reconstruction restores the responder without changing logical focus.
GTK4 and WinUI mount Tree roles through focusable list/list-item primitives for
this flattened native projection. AppKit rebuilds its row list without hidden
descendants. Hierarchy and expansion semantics remain in the portable keyed
runtime, so adapter widget choice does not change the keyboard contract.
Native event routing tries the target widget first, then mounted ancestors, so
child widget callbacks can reach container-level handlers. Native Close is
target-scoped so closing a nested dialog cannot also close its containing
window. A selection-container
array is authoritative: it replaces the previous selection after native values
are resolved to stable element keys, then projects the complete state to every
affected row before action dispatch. GTK4 and WinUI native ListBox callbacks
produce these arrays from all currently selected rows. AppKit sends the row
value with its modifier context and the portable selection manager produces the
same aggregate snapshot. Scalar selection values remain compatibility input.
Selection events with missing or empty values are normalized from the selected
child's value or label; native selection-container events also infer the current
selected child when the host callback does not include a useful payload.
Text-field change values are clamped to maxLength. Initial, rerendered, and
event-provided number-input and ranged-control values are clamped to min/max
range bounds and snapped to step hints before platform rendering, interaction
state, or action dispatch. Number-shaped text fields parse initial and event
values with their inherited locale before emitting a canonical finite decimal
to reducers; sliders retain their platform-neutral numeric payload contract.
Missing, empty, non-finite, or otherwise
unparseable initial numeric values are omitted from native value state; matching
change payloads are treated as no-ops for number inputs and ranged controls,
preserving the last valid value. Protocol hosts and native backends therefore
share the same value contract.
Empty event action ids are ignored rather than dispatched.
ActionRegistry records and validates non-empty action ids before they are
handed to the Rust state reducer. Hosts that implement
AccessibilityTreeHost can expose the rendered tree through
GuiRuntime::accessibility_tree() for tests, protocol inspection, and native
accessibility integration. Runtime export overlays interaction state, so changed
values, checked state, selection, focus, read-only state, multiple-selection
mode, and host node ids are visible to protocol consumers.
Single-selection containers with no explicit value derive their rendered
accessibility value from the selected or checked child, keeping initial
selection state and later native selection callbacks on the same value shape.
Invisible target widgets, and descendants of invisible widgets, suppress native
events before interaction state or action routing and are omitted from rendered
accessibility tree projection. Invisibility currently includes HTML hidden,
CSS display: none, visibility: hidden / collapse,
content-visibility: hidden, and closed intrinsic dialogs. Inert widgets use
the same subtree event and accessibility suppression; both the HTML inert
attribute and CSS interactivity: inert feed that native inert state.
Disabled target widgets, and descendants of disabled widgets, suppress user
activation, value, selection, toggle, and keyboard events before interaction
state or action routing, while focus and blur events can still update
inspection state when a host reports them.
Read-only target widgets suppress value, selection, and toggle events after
keyboard activation normalization. Read-only selection containers also suppress
value-changing events from selectable descendants they own, while focus, blur,
press, and explicit keyboard events can still route to actions.
ListBox selection projection uses that multiple-selection flag to keep existing
selected children in multi-select lists while single-select lists follow the
latest selected child. On the first render without prior focus history, the
first autoFocus control in a renderable, non-disabled subtree initializes
runtime focus state for accessibility projection. After the complete tree is
mounted and its root is attached, the runtime resolves the actual focusable
target and emits the same typed RequestFocus command used by imperative
navigation. autoFocus is therefore not a native widget setter. AppKit uses
makeFirstResponder, GTK4 uses grab_focus, and WinUI calls the fixed
IUIElement::Focus(Programmatic) ABI through a small audited adapter because
winio-winui3 0.4.2 leaves that method unwrapped.
Imperative focus navigation uses a separate ProgrammaticFocusHost capability
and a serialized PlatformCommand::RequestFocus operation rather than a
widget-property setter. GuiRuntime validates mounted focusability, resolves
first/last/next/previous order, and constrains requests to the active contained
scope before issuing the command. AppKit maps it to makeFirstResponder, GTK4
maps it to grab_focus, and WinUI maps it to IUIElement::Focus with the
programmatic focus state. Native focus callbacks still authoritatively update
interaction state and route focus actions. When that callback omits modality,
the matching imperative request supplies a one-shot virtual modality so
focus-visible state remains deterministic. The runtime retains the last native
focus owner across the blur/focus transition, redirects focus events that
escape an active contained scope, and records the pre-mount focus owner for
each restore-enabled scope. When nested scopes unmount, valid restoration
targets unwind from the innermost scope outward through the same imperative
host capability.
MountedOverlayRegistry is the platform-independent source of truth for open
managed overlays. After keyed reconciliation, GuiRuntime synchronizes the
registry from data-overlay contracts, preserves already-open overlays in
activation order, and appends newly opened overlays. Persistent sibling nodes
therefore stack by when they became visible rather than by source order.
While any overlay is active, the runtime projects an internal capture marker
to mounted native nodes. The shared native interaction profile translates that
marker into pointer-lifecycle and key-down subscriptions on AppKit, GTK4, and
WinUI. Escape is claimed only by the topmost overlay unless
isKeyboardDismissDisabled is set. Outside dismissal records the topmost
overlay on press start and closes it only when release is also outside that
same overlay; intercepted background events do not reach application actions.
Close-on-blur requires a known relatedTarget outside the overlay subtree.
All successful dismissal paths synthesize a target-scoped native Close
event, which routes to the overlay's onClose action without closing an
ancestor layer.
For the topmost active modal, branches outside the modal and any overlays opened after it are projected inert. Inert projection also removes those branches from portable accessibility output. Ancestors needed to retain the native hierarchy stay enabled, but the registry still suppresses interactions targeted outside the modal foreground. Later portaled overlays are treated as foreground and may receive focus through an earlier contained scope.
Overlay focus uses the existing FocusManager contracts. A newly opened
overlay with autoFocus focuses its first tabbable or focusable descendant;
contained focus remains inside the active scope; and unmounting a
restore-enabled overlay returns focus to its valid trigger. UiDialog,
UiModal, and the default UiPopover opt into these behaviors. A nonmodal
popover keeps background branches interactive and does not contain focus,
while still supporting autofocus, restoration, close-on-blur, and Escape.
Anchored overlay positioning is a separate typed host capability. Popover and
Tooltip authoring props serialize a data-overlay-position contract containing
placement, main/cross offsets, flip/update policy, boundary padding, arrow
geometry, and maximum height. After lifecycle projection, GuiRuntime resolves
each open Popover to an explicit mounted anchor id/key, its nearest marked
trigger context, a previous trigger sibling, or a non-window parent. Invalid,
ambiguous, self, and descendant anchors are rejected before platform commands
are emitted. PlatformPlanningHost retains the relationship for recovery
replay and suppresses identical commands when shouldUpdatePosition is false.
Protocol v1 carries the same validated options in positionOverlay.
Non-focus interaction state is
revision-scoped: after a successful rerender, controlled values from the new
blueprint supersede stale local event state while focus remains preserved until
a blur or another focus event arrives. Native focus history is retained even
when the focused node is later removed, so rerenders do not reapply autoFocus
after the platform has taken focus ownership. Later native events on that node
also rebase their interaction baseline from the latest blueprint before
applying local changes.
Stateful style projection uses that same revision model. The runtime resolves
supported variant declarations against transient interaction state and current
typed props, preserves declaration-level source order, and caches the last
resolved style per mounted node. A state transition batches focused
update_portable_style calls inside a host frame. Planning hosts translate
them to normal blueprint updates and SetPortableStyle setters, so AppKit,
GTK4, WinUI, command-executing, and headless hosts share one path. A failed
projection rolls the host frame, interaction state, selection state, and the
previous resolved styles back together. Style requirements also participate in
native event subscription planning, so a visual hover or press variant works
without a user action callback.
CSS style attribute text is parsed by src/css_text.rs before it enters
WebProps. The parser splits declarations only on top-level CSS separators, so
URLs, strings, bracketed values, and function arguments can contain : or ;
without corrupting the declaration map. It also strips top-level !important
priority markers from values and applies important declarations after normal
declarations.
For embedded Rust hosts, native backends can expose callbacks through
NativeEventSource. GuiRuntime::dispatch_pending_native_events() drains the
host event queue, applies interaction state updates for every event, and returns
the action invocations from events that have bound RSX actions. State-only events
such as focus or blur do not interrupt the batch.
GuiRuntime::handle_pending_native_event_results() keeps the same drain boundary
but returns one result per native event, including optional action invocations
and the interaction state changes caused by that event. Those handled event
results are serializable for host logging and process boundaries. Events for
host node ids that no longer belong to the current render tree are treated as
empty handled events, so a stale native callback from a previous frame cannot
abort the remaining drain batch. Protocol hosts can still send explicit
HostEvent records. Keyboard event values can carry the platform key or
shortcut token; the runtime normalizes common native key names into canonical
payloads such as Enter, Tab, Backspace, Escape, arrow keys, and a single
space for Space. Use the handle_* event APIs when the caller needs per-event
results, including events that only update runtime or accessibility state.
NativeRuntimeApp builds on that embedded runtime path: it owns
GuiRuntime<H>, drains pending native events from hosts that implement
NativeEventHost, applies action invocations to application state through a
reducer, and renders the next UiFrame back into the same host after
state-changing events. Existing reducers return GuiResult<()> and therefore
continue through the complete target-to-ancestor route. Propagation-aware apps
use new_with_propagation and return ActionPropagation from the reducer. A
Stop result is sticky for that native event: every callback registered on the
same currentTarget still runs in deterministic order, then callbacks on
ancestors are discarded. The retained invocation prefix is reflected in both
the response and ActionRegistry history, and event responses expose
propagationStoppedAt for inspection. NativeProtocolApp implements the same
contract at the serialized host boundary.
handle_pending_native_events_while stops draining the
current native event batch as soon as the supplied state predicate returns
false, which keeps queued follow-up events from mutating state after an app-level
close action. handle_pending_native_event_batch_while uses the same behavior
but returns a NativeRuntimeEventBatch with host-drain, queued, handled,
buffered, and predicate-stop diagnostics for app-loop logging and native
automation assertions. Unprocessed events are kept inside the app and are
handled before new host events on the next drain. When the predicate is already
false before the drain starts, the host event queue is left untouched so callers
can pause and resume later.
Platform-native app specializations such as
AppKitRuntimeApp, WinUiRuntimeApp, and Gtk4RuntimeApp add the OS event
pump and use the same bounded drain while stopping their run_*_while loops
when the root window closes or the state predicate exits. Their
pump_*_event_batch_while helpers combine the pre-pump and post-pump A3S event
drains into one NativeRuntimeEventBatch, which gives native automation a
single assertion point for OS event pump cycles. Each backend also exposes
*_with_propagation constructors and propagation-aware pump/run variants so
the opt-in contract is preserved through the real OS event loop.
Window close lifecycle events use the same action path. UiFrame.window.onClose
wraps the rendered root in a native window with an onClose event binding, and
NativeEventKind::Close dispatches that action id. AppKit and GTK native
surfaces enqueue close events from native window, panel, and dialog callbacks.
WinUI installs a small HWND subclass for root windows and registers
ContentDialog::Closing for dialogs, then enqueues the same close event while
clearing the surface's open-dialog tracking so a dismissed dialog can be shown
again on a later state-driven render. When those queued dialog close events are
drained, the surface also releases the retained ShowAsync operation for that
dialog. The backend still observes the root window handle for app-loop shutdown.
Reducers run synchronously on the UI thread and must remain fast state
transitions. They must not read files, call network services, wait for child
processes, create an async runtime, or use block_on. Application I/O belongs
outside the reducer in an application-owned EffectRuntime (or another
injected executor); completed work returns a message/result that is merged into
state on the UI thread.
EffectRuntime provides the implemented supervision boundary:
- in-flight work and the completion channel are both bounded; defaults are 32
active effects and 256 queued completions, and
with_limitsconfigures both - spawning beyond the in-flight limit returns a contextual error, while the synchronous completion channel provides backpressure
EffectCancellationsupports cooperative cancellation by id; cancelling or dropping the runtime prevents later completion delivery and signals all active tasks. A cancelled task keeps its in-flight slot until the worker reports termination, so a task that ignores cancellation cannot bypass the concurrency bound- the default
ThreadEffectExecutorcatches task panics and converts them intoGuiErrorcompletions. It returns a joinableEffectWorker; runtime shutdown disconnects the bounded completion channel and joins every worker, so even a non-cooperative task cannot escape the owning application lifetime EffectExecutorandEffectWakerlet applications provide Tokio-backed work and wake their owning event loop without adding Tokio to the GUI core. A custom executor may return a detached worker only when an external owned task scope provides the equivalent shutdown guarantee
NativeRuntimeApp::with_background_updates is the UI-thread integration seam.
Its poller drains a batch of completed work, mutates application state, and
returns BackgroundUpdate. poll_background_updates renders exactly once when
that batch reports any state change, regardless of how many completions it
merged, and separately tracks whether more work remains pending.
The AppKit, GTK4, and WinUI run_*_while loops use that pending flag to choose
their event-pump policy. They poll native events while background work remains,
then park for at most one 16 ms frame instead of busy-spinning.
background_effect_waker() supplies the matching EffectWaker, so a completed
worker unparks the UI thread immediately. When no work is pending the loop uses
the platform's blocking wait. Each iteration polls background completions
before the OS event pump and rechecks the root-window and application exit
predicates before waiting.
The RSX/native host boundary uses serializable protocol types:
UiFrame: a compiled RSX tree plus action ids.WindowOptions: optional native window title, close action id, initial dimensions, min/max dimensions, and resizable flag for a frame.RenderedFrame: the native root node produced by rendering a frame.NativeRenderResponse: the native root plus the incremental platform commands and rendered accessibility tree emitted by a render pass.HostEvent: a native event emitted by a platform backend.HostEventResponse: the validated action invocation for that event plus any interaction state changes produced while handling it.NativeHostEventResponse: the optional action invocation, rendered accessibility tree, and interaction changes for host events that may only update runtime state.NativeProtocolApp: a reusable host-side state loop that buildsUiFramevalues from application state, applies action invocations through a reducer, and returns follow-up render responses after state-changing events.NativeRuntimeApp: the embedded equivalent for Rust-owned native hosts; it handles queued native events, applies reducer updates, and rerenders directly into the ownedGuiRuntime.NativeRuntimeEventBatch: the diagnostic result for embedded pending-event drains, including per-event responses plus host-drain, handled, buffered, and predicate-stop counts.
The protocol decouples input generation from platform backend execution. RSX components do not see native widget handles; native backends receive serialized protocol records. The same rule applies to native rendering commands: platform hosts receive serializable command records and blueprints, not component runtime instances.
NativeProtocolSession is a serializable host boundary for a native platform
process. It owns a GuiRuntime<PlatformPlanningHost<_>>, accepts UiFrame
values, returns only the new PlatformCommand records for that render pass,
and dispatches HostEvent values back to registered action ids. A native
host can keep one session per native surface or window and apply the returned
commands on its UI thread. Rendering a new UiFrame replaces the session's
registered action scope with that frame's actions list after the native render
succeeds, so stale action ids from earlier frames cannot be invoked by later
host events and failed renders keep the previous action scope. If raw protocol
JSON omits actions, Rust infers action ids from compiled event props and
optional actionLabels. Explicit actions, including an empty list, remain
authoritative.
Protocol v1 is the strict transport contract. Its request, command,
accessibility, control-state, event, and response types are dedicated *V1
DTOs with unknown-field rejection; they do not serialize runtime structs behind
opaque JSON values. A session permanently selects either legacy or strict-v1
mode on first use. Strict-v1 validates the protocol version, session id, render
revision, event sequence, command sequence, and acknowledgement before any
state transition. Each rendered response is retained until its exact ACK;
retrying the same revision returns the retained response, while later renders
and events are rejected until delivery is acknowledged. Password values remain
available to in-process reducers but are removed from commands, accessibility,
responses, session debug output, and retained diagnostics.
NativeProtocolApp remains the convenience state/reducer loop for the legacy
in-process API. Strict-v1 is deliberately a transport-owned
NativeProtocolSession primitive: the transport owns resend/ACK ordering and
the product owns reducer, effect, and next-frame orchestration.
When UiFrame.window is present, the Rust core wraps the compiled RSX root in
a NativeRole::Window. The same command stream then creates NSWindow,
Microsoft.UI.Xaml.Window, or gtk::ApplicationWindow before inserting the
frame content as its child. Window title, initial dimensions, min/max
dimensions, and the resizable flag are projected into the native blueprint/config
path; the resizable value is also retained as Web metadata for hosts that still
read protocol attributes.
PlatformPlanningHost records the exact commands a real platform backend needs
to execute:
{"type": "create", "id": ..., "blueprint": ...}{"type": "update", "id": ..., "blueprint": ...}{"type": "insertChild", "parent": ..., "child": ..., "index": ...}{"type": "remove", "id": ...}{"type": "setRoot", "id": ...}{"type": "requestFocus", "id": ...}{"type": "positionOverlay", "overlay": ..., "anchor": ..., "request": ...}{"type": "accessibilityAnnouncement", "announcement": {"node": ..., "message": ..., "priority": "assertive"}}
Strict protocol v1 flattens the same announcement payload beside its command
type (node, message, and priority) and validates a non-zero target plus a
non-empty message during decoding.
The planning host's command vector is a pending delivery queue, not cumulative
diagnostic history. take_commands() moves all commands produced since the
previous drain and leaves the queue empty. Legacy session calls therefore
return only the new commands for each render pass. Strict-v1 moves those
commands into its retained response and does not release that delivery record
until ACK.
Diagnostic histories owned by the runtime are bounded independently. Action
invocations, interaction changes, headless operations, successfully executed
driver/recording commands, and native setter diagnostics default to the most
recent 256 entries. Their limits are configurable, zero disables retention
without disabling dispatch/execution, and the corresponding take_* APIs
transfer retained records out and clear the history. Sensitive control values
are redacted before retention. Sensitivity is resolved defensively from the
explicit marker, typed password widget kind, structured input type, or legacy
metadata["type"]. Blueprint serialization, Debug, command histories, and
setter histories use the same decision and remove duplicated value,
defaultValue, ARIA-value, and data-value metadata channels. Low-level action
registry calls redact values by default unless the caller explicitly supplies a
public sensitivity classification, while runtime/app Debug output reports
types and queue counts rather than live state or event payloads. These histories
exist for inspection and testing; they are not application event stores.
Credential-bearing metadata and resource-policy fields, including CSP nonces
and inline srcdoc, are always removed from diagnostic projections regardless
of control value sensitivity; live rendering and protocol input retain them.
This keeps keyed reconciliation in the Rust core and keeps platform adapters
focused on native object lifetime and thread-affine UI work.
NativeWidgetBlueprint carries the platform family (appKit, winUI, gtk4),
typed NativeWidgetKind, a diagnostic legacy class name, native role,
accessibility role, independent visible label and computed
accessibilityLabel, value/action
bindings, semantic control state, Web metadata, event bindings, and parsed
portable style tokens. It is safe to send to a platform process or language
binding as JSON.
Portable style parsing stores normalized CSS declarations, CSS custom
properties, and Tailwind variant declarations before projecting supported values
into native style tokens. Recognized Tailwind utility classes are applied first
and inline style declarations second, preserving inline style precedence.
Unmapped CSS declarations are retained in PortableStyle::unsupported and in
the raw blueprint style map.
Container roles with overflow, overflow-x, overflow-y, logical overflow,
or Tailwind overflow utilities set to auto or scroll are planned as native
scroll shells. AppKit uses NSScrollView with an internal NSStackView, WinUI
uses ScrollViewer with an internal StackPanel, and GTK4 uses
gtk::ScrolledWindow with an internal gtk::Box. Children continue to mount
inside the internal container, while the outer scroll widget is what the parent
native view owns. Native surface update paths keep the scroll policy, inner
container orientation, spacing, and explicit viewport size in sync across
rerenders.
Native bindings can call blueprint.config() to derive a NativeWidgetConfig
with setter-oriented values such as enabled, visible, placeholder,
range bounds, range step state, selected/checked state, read-only state,
multiple-selection state, autofocus state, text-entry hints, text-length hints,
textarea sizing hints, window resizability, event action ids, metadata, and
portable style. This keeps AppKit, WinUI, and GTK bindings from reinterpreting
protocol fields differently.
NativeWidgetConfig::diff() returns an ordered NativeWidgetSetterBatch for
update passes (NativeWidgetConfigPatch is only the compatibility alias), and
HandleWidgetDriver stores the last config for each handle so
NativeHandleAdapter::update_handle_config() can apply only changed setters.
NativeWidgetConfig::create_setters() and NativeWidgetSetterBatch::setters()
produce NativeWidgetSetter operations such as SetLabel,
SetAccessibilityLabel, SetEnabled, SetVisible, SetPlaceholder,
SetMinimum, SetMaximum, SetCurrent, SetStep, SetWindowResizable,
SetEvents, SetPortableStyle, and SetMetadata. Platform bindings can map
those operations to the corresponding AppKit, WinUI, or GTK property setters.
The semantic mapper never substitutes aria-label for visible content.
Instead, the accessible label carries the explicit aria-label and falls back
to the visible label when no override exists. Accessibility trees, live-region
text, recording backends, and strict protocol v1 use that same effective name.
AppKit maps the setter to setAccessibilityLabel, GTK4 maps it to the
GtkAccessible label property, and WinUI maps it to
AutomationProperties.SetName. Capability overrides retain conservative
portable-only reporting for native wrappers that cannot accept an independent
name: AppKit logical list/tree and tab items, GTK4 gio::MenuItem, and the
WinUI window wrapper.
The feature-gated handle adapters keep a bounded, redacted setter history in
their handle state, so tests exercise the same create/update flow real native
bindings map to OS controls without accumulating secrets or unbounded logs.
CommandExecutingHost wraps this command stream around a
PlatformCommandExecutor. A render frame checkpoints planning, prepares its
complete immutable PlatformCommandBatch, commits it, validates the explicit
PlatformBatchAck, and only then consumes the acknowledged queue. Prepare
failure preserves the exact frame and native state. A commit or invalid ACK is
conservatively treated as potentially partial: the host enters
DegradedNativeState, rejects incremental work, rolls planning back to the last
acknowledged snapshot, and requires recover_with_executor to replay a full
create/insert/set-root snapshot into a fresh executor before resuming.
DriverCommandExecutor is the reusable executor for real native backends: it
validates that a blueprint targets the driver's backend and delegates command
effects to NativeWidgetDriver. Platform executors must own their native
resources and release them when dropped; recovery drops the old partial
executor before replaying into the replacement so two surfaces are not active
at once. Real-platform failure-injection and teardown automation remain a
hardening gate in addition to the deterministic Rust executor tests.
Create commands must introduce a new host id; duplicate create ids are rejected
before native handles or recorded backend objects are replaced.
NativeWidgetDriver is the OS-binding contract. A platform layer implements:
create_widget(id, blueprint)update_widget(id, blueprint)insert_child(parent, child, index)remove_widget(id)set_root_widget(id)request_focus(id)position_overlay(overlay, anchor, request)announce_accessibility(announcement)
If the platform layer owns event callbacks, it also implements
NativeEventSource::take_native_events() and queues NativeEvent records such
as press, change, focus, key down, key up, and selection change.
That keeps command decoding, backend validation, and render order in shared Rust while letting AppKit, WinUI, and GTK bindings own their thread-affine handles and native callback registration.
For platform bindings that already expose clonable native handle wrappers,
HandleWidgetDriver provides the storage and command glue. The platform only
implements NativeHandleAdapter, returning handles from create_handle and
applying updates, child attachment, removal, and root assignment to those
handles. This path supports NSView, WinUI object, and GTK object wrappers.
Stored handles, configs, and root state are changed only after the adapter
operation succeeds, so backend failures do not desynchronize the shared driver.
Adapters can implement update_handle_config to receive the
typed config patch for native setter updates, or use the default full-blueprint
update method while bootstrapping. The feature modules expose AppKitHandleAdapter,
WinUiHandleAdapter, and Gtk4HandleAdapter surfaces for that path.
For platform bindings that prefer a setter-first SDK boundary,
NativeWidgetSurface is the lower-level contract. It creates one native widget
handle from a blueprint, applies each NativeWidgetSetter, inserts native
children, removes native widgets, and sets the root surface. SurfaceHandleAdapter
wraps that lower-level surface in NativeHandleAdapter, so real SDK bindings can
focus on calls such as setTitle, setEnabled, setText, addSubview,
Append, or append while the shared Rust driver still owns reconciliation,
config diffing, command order, and event routing.
The included RecordingBackend applies commands to a pure Rust object tree.
Feature-gated platform executor surfaces:
appkit: maps classes such asNSButton,NSTextField(input),NSSearchField,NSSecureTextField, andNSTextField(textarea)into AppKit object kinds behindAppKitWidgetDriverand replays native setter operations throughAppKitHandleAdapter.appkit-native: usesobjc2AppKit bindings on macOS to create realNSWindow,NSPanel,NSView,NSButton,NSTextField,NSSwitch,NSStackView,NSComboBox,NSScrollView,NSTabView,NSTabViewItem,NSBox(separator),NSSlider, andNSProgressIndicatorobjects throughAppKitNativeSurface. Window and panel setters apply content size plus min/max size constraints fromUiFrame.windowand portable style tokens. The shared renderer still owns reconciliation and config diffs; the surface applies typedNativeWidgetSetteroperations directly to AppKit controls. The backend creates in-process AppKit controls directly.NSButtoncontrols are wired to Objective-C target/action callbacks that enqueueNativeEventKind::Pressrecords, and editableNSTextFieldcontrols, including nativeNSSearchFieldsearch inputs and textarea-shaped fields, use anNSTextFieldDelegateto apply max-length hints and textarea sizing hints, including rerenders that removerows/colsand return to the native default size. They enqueue focus, change, and blur records, with change events carrying the current native string value. AppKit text fields also map portable text-entry hints into native spell-checking, correction, completion, inline prediction, automatic text completion, and character-picker settings when the control responds to the matching selector.NSButton(checkbox)andNSSwitchcontrols apply native checked state and enqueueNativeEventKind::Togglerecords with the current boolean value.RadioGroupuses nativeNSStackViewcontainers withNSButton(radio)children, and selected radios apply native checked state.NSComboBoxcontrols receiveListBoxItemchildren as native AppKit object values and enqueueNativeEventKind::SelectionChangerecords. Independent semanticListBoxtrees create nativeNSScrollViewcontainers with AppKit row controls and enqueue selection-change records with the selected item value. SemanticToolbartrees create nativeNSStackViewcontainers for tool controls. SemanticDialogtrees create nativeNSPanelwindows whose content views are populated with native children; panels are presented from native visibility state instead of being inserted into parent stack layout. SemanticPopovertrees create nativeNSPopoveroverlays whose content view controllers hold native children. Typed positioning selects an explicit anchorNSView, resolves logical placement against direction, and projects a positioning point, preferred edge, main offset, and cross offset. SemanticTabstrees foldTabListand orderedTabPanelchildren into nativeNSTabViewItemobjects whose content views are the panel views;NSTabViewDelegatecallbacks enqueue tab selection-change records. SemanticMenutrees create nativeNSMenuobjects withNSMenuItemchildren; top-level menus are installed as the application main menu instead of being inserted as views, and menu item target/action callbacks enqueue press records.Separatorcreates nativeNSBoxseparators.NSSlidercontrols apply native orientation and step hints and enqueue rangedNativeEventKind::Changerecords with the current double value, whileNSProgressIndicatoris updated by setter-driven ranged state. RuntimeautoFocuscommands arrive after the target view is attached to a window and usemakeFirstResponder, so the opened window starts on the intended native control when AppKit accepts the responder. The AppKit event pump also maps key-down and key-upNSEventrecords to the focused native node, normalizes common AppKit key codes, and falls back to the root node for window-level keyboard routes.AppKitRuntimeAppprovides the embedded app loop for this backend: it renders into anAppKitNativeSurface, pumps AppKit events, drains queued A3S native events, runs the application reducer, and rerenders the next frame. The loop also observes root AppKit window and panel close notifications, enqueuesNativeEventKind::Closeforwindow.onCloseactions, and letsrun_appkit_whilestop when the user closes the surface. Theappkit_controlsexample is the AppKit instance of the shared native controls smoke harness for text input, toggles, sliders, selects, tabs, actions, rerenders, and root-window close exit. Theappkit_dogfoodexample runs the shared task editor and review workflow with menu commands, dialog visibility, checklists, disabled gates, read-only status, keyboard shortcuts, window close lifecycle actions, and state-driven app loop exit throughrun_appkit_while. These event paths flow through the existingNativeEventSourceboundary.winui: maps classes such asMicrosoft.UI.Xaml.Controls.Button,Microsoft.UI.Xaml.Controls.TextBox,Microsoft.UI.Xaml.Controls.TextBox(search),Microsoft.UI.Xaml.Controls.PasswordBox, andMicrosoft.UI.Xaml.Controls.TextBox(textarea)into WinUI object kinds behindWinUiWidgetDriverand replays native setter operations throughWinUiHandleAdapter.winui-native: useswinio-winui3and the Windows App SDK on Windows to create real WinUI 3Window,StackPanel,TextBlock,Button,TextBox,CheckBox,RadioButton,ComboBox,ListBox,ContentDialog,ToolTip,Grid,TabView,TabViewItem,Border(separator),Slider, andProgressBarobjects throughWinUiNativeSurface. The backend creates WinUI controls directly. WinUI windows apply initialUiFrame.window.width/heightwithSetWindowPos, min/max resize bounds throughWM_GETMINMAXINFO, andUiFrame.window.resizablethrough HWND style updates so fixed and bounded windows match AppKit and GTK behavior. textarea-shaped text boxes enable return input and wrapping. WinUI text boxes applymaxLengthupdates, including rerenders that remove the limit, clamp programmatic text writes and change payloads to the active limit, and saturate protocol values at WinUI's signed integer boundary. Text inputs track the last controlled value so read-only callbacks can roll native edits back without enqueueing change events. They also map portable text-entry hints into native spell-check, text-prediction, programmatic keyboard display, and color-font settings where the currentwinio-winui3bindings expose those APIs. WinUI callbacks enqueue press, change, focus, blur, toggle, selection-change, and ranged value events through the sameNativeEventSourceand action routing path as AppKit and GTK. SemanticTabstrees foldTabListand orderedTabPanelchildren into nativeTabViewItemobjects and route WinUI tab selection changes with the serialized selection value when one is available.Separatoruses a native XAMLBorderloaded through WinUI's markup runtime. SemanticToolbartrees create horizontal nativeStackPanelcontainers, keeping toolbar children as real XAML controls. SemanticDialogtrees create nativeContentDialogcontrols whose content is populated with real XAML children; dialog nodes are overlay-mounted and shown withShowAsyncfrom native visibility state after the root window exposes aXamlRoot. SemanticPopovertrees create ToolTip-backed native overlay surfaces with real XAML children becausewinio-winui30.4.2 does not expose a strongFlyoutbinding yet. Position commands bindPlacementTarget, a direction-aware placement point, signed offsets, and optional maximum height. WinUI remains responsible for choosing the final collision side. SemanticMenutrees use native XAMLStackPanelmenu surfaces with nativeButtonmenu items whilewinio-winui30.4.2 lacks strongMenuFlyoutandMenuBarbindings. The semanticSwitchrole keeps its nativeSwitchsemantic in the IR; withwinio-winui30.4.2, the native surface temporarily backs that state with a WinUICheckBoxbecause the generated bindings do not exposeToggleSwitchyet. WinUI pointer handlers opt into handled routed events so Button-backed controls preserve mouse, pen, and touch press phases that XAML class handling would otherwise hide. Preview key-down and key-up handlers likewise feed the shared keyboard state machine before Button activation, avoiding duplicate semantic presses from the laterClickcallback.winio-winui30.4.2 does not generate these registration methods, so the native surface isolates the fixed WinRT ABI calls in a small adapter. The same binding version leaves programmaticFocusunwrapped; that call uses the same isolated ABI approach while WinUI focus callbacks remain the observable event source. It also omits theMicrosoft.UI.Xaml.Automationnamespace, so the stableIAutomationPropertiesStatics::SetNameABI is isolated in its own adapter rather than leaking raw vtable access into widget updates. Window close requests are observed through the HWND message path: the surface installs a close-event subclass on each WinUI window and enqueuesNativeEventKind::ClosewhenWM_CLOSEarrives. Content dialogs register WinUI'sClosingcallback and enqueue the same event for dialogonCloseactions while clearing local open-dialog tracking. Draining those native close events releases the retainedShowAsyncoperation; programmatic hides are suppressed during render-driven teardown. WinUI surfaces are created inside the XAML-ownedApplication::Startlifecycle.run_winui_application_staged_asyncrenders and activates the first window duringOnLaunched, then polls the application future throughDispatcherQueueturns after launch returns.WinUiRuntimeAppdrains queued A3S native events, runs the reducer, rerenders the next frame, and observes the root window sorun_winui_while_asynccan stop when the user closes the surface without blocking XAML layout or input dispatch. Thewinui_controlsexample runs the same shared native controls smoke frame against real WinUI widgets. Thewinui_dogfoodexample runs the shared task editor and review workflow through the same reducer loop and WinUI event queue, including window close lifecycle actions and state-driven app loop exit throughrun_winui_while_async.gtk4: maps classes such asgtk::Button,gtk::Entry,gtk::SearchEntry,gtk::PasswordEntry,gtk::SpinButton, andgtk::TextViewinto GTK object kinds behindGtk4WidgetDriverand replays native setter operations throughGtk4HandleAdapter.gtk4-native: usesgtk4-rson Linux to create realgtk::ApplicationWindow,gtk::Box,gtk::Label,gtk::Button,gtk::Entry,gtk::SearchEntry,gtk::PasswordEntry,gtk::SpinButton,gtk::TextView,gtk::CheckButton,gtk::Switch,gtk::DropDown,gtk::ListBox,gtk::ListBoxRow,gtk::Dialog,gtk::Popover,gtk::PopoverMenuBar,gio::Menu,gio::MenuItem,gtk::Notebook,gtk::Separator,gtk::Scale, andgtk::ProgressBarobjects throughGtk4NativeSurface. SemanticToolbartrees use nativegtk::Box(toolbar)containers. SemanticDialogtrees use nativegtk::Dialogwindows and content areas; dialog nodes are presented from native visibility state instead of being inserted into parent box layout. SemanticPopovertrees use nativegtk::Popoveroverlays with native GTK children. Position commands reparent the popover to the resolved anchor, set its physical side and pointing rectangle, and apply signed main/cross offsets; GTK performs final work-area collision handling. SemanticMenutrees use nativegio::Menumodels,gio::MenuItemchildren,gtk::PopoverMenuBarsurfaces, andgio::SimpleActionactivation callbacks. SemanticTabstrees become native notebook pages with source tab labels, native panel widgets, and selection-change events carrying the selected tab value when available. GTK text entries, search entries, password entries, spin buttons, and textarea-shapedTextViewcontrols apply the relevant read-only and sizing setters.TextViewsizing rerenders clear removedrows/colshints while preserving axes that are explicitly sized by portable style. Search/password/text entries andTextViewcontrols also apply placeholder and max-length where GTK exposes that behavior. GTK spin buttons preserve numeric range, current, and step state and emit change events with the current numeric value. GTKEntryandTextViewcontrols also map portable text-entry hints such asinputMode,input type,autoCapitalize,autoCorrect,virtualKeyboardPolicy, andspellCheckinto GTK input purpose and input hint flags. It is a Linux-only feature so macOS and Windows builds can enable all portable features without linking GTK. Linux builds require GTK 4.14 or newer development libraries andpkg-config; 4.14 provides the native accessible announcement API used by the typed announcement command. GTK callbacks enqueue press, text change, focus, blur, toggle, and selection-change events through the sameNativeEventSourceand action routing path as AppKit. GTK widgets also attachEventControllerKeycontrollers so native key-down and key-up events carry normalized key values through that same queue. RuntimeautoFocuscommands arrive after the target widget is attached and usegrab_focuswhen GTK accepts the focus change.Gtk4RuntimeAppprovides the embedded app loop for this backend: it registers a GTK application, renders intoGtk4NativeSurface, pumps the default GLib main context, drains queued A3S native events, runs the application reducer, rerenders the next frame, and observes root window/dialog close notifications. Those close callbacks enqueueNativeEventKind::Closeforwindow.onCloseactions and letrun_gtk4_whilestop when the user closes the surface. Thegtk4_controlsexample runs the same shared native controls smoke frame against real GTK4 widgets. Thegtk4_dogfoodexample runs the shared task editor and review workflow through the same reducer loop and GTK event queue, including window close lifecycle actions and state-driven app loop exit throughrun_gtk4_while.
Source files are grouped by layer:
src/native.rsandsrc/native/define the platform-neutral native element IR: roles, keys, props, and prop builders. They do not create OS widgets.src/platform/owns native blueprint types, config diffs, platform adapters, planning, and widget-name mapping. It answers "what widget class should this native element become on this platform?"src/appkit.rs,src/gtk4.rs, andsrc/winui.rscontain lightweight platform mapping and handle-driver code. They are useful for planning, recording, and tests that should not instantiate real OS widgets.src/appkit_native/,src/gtk4_native/, andsrc/winui_native/are the real OS surface implementations. They implementNativeWidgetSurface, create AppKit/GTK/WinUI widgets, attach native events, and run the embedded app loops.src/native_backends/is private support code for those real native surfaces, not a competing backend layer. It currently holds menu-specific helpers:native_backends/appkit/menu.rsowns AppKit menu parent/child tracking,native_backends/winui/menu.rsowns the WinUI menu fallback policy, andnative_backends/gtk4/menu.rsowns GTK menu models, menu item actions, and model rebuilds.
src/backend/ owns the command executor, handle driver, surface adapter,
recording backend, and backend traits. Platform-specific native surface
directories keep their module entry point separate from the NativeWidgetSurface
implementation, and WinUI keeps setter and event helpers in a dedicated helper
module.
Style normalization lives under src/style/. src/style/mod.rs is the module
entry point. portable.rs, apply.rs, shorthands.rs, composition.rs,
tailwind_apply.rs, and declarations.rs split the PortableStyle data model
from CSS property projection and declaration bookkeeping. types.rs owns the
portable style value types, and parsing.rs owns CSS property parsers.
Tailwind utility-to-declaration mapping is split under src/style/tailwind_utilities/
by utility family. src/style/value_parsing.rs owns length and time token
recognition, including CSS custom properties, CSS math functions, and units that
must be preserved instead of converted eagerly. src/style/color_parsing.rs
owns hex, RGB/HSL, modern CSS color function, and background-shorthand color
recognition.
The detailed portable CSS and Tailwind projection contract lives in
style-contract.md. Keeping it separate makes the runtime
and protocol architecture easier to review while preserving one authoritative
list of supported native style semantics.