# oxiui (facade) TODO
## Status
Working facade crate (~403 SLOC). Provides `App` builder with `.theme()`, `.content()`, `.backend()`, `.run()` API. Supports egui backend (default, via eframe), iced backend (feature-gated), headless mode (`run_headless_once`). Re-exports core types, theme constructors, table module, accessibility module, and web module (all feature-gated). The facade successfully runs GUI apps on desktop (macOS/Linux/Windows). Main gaps: no window configuration beyond title/size, no multi-window, no lifecycle hooks, no plugin system, no app state management.
## Core Implementation
- [x] Window configuration: `App::window()` builder with `inner_size`, `min_size`, `max_size`, `resizable`, `decorations`, `transparent`, `always_on_top`, `icon`, `position`, `fullscreen` (~100 SLOC)
- **Completed:** Integration first — egui `MockUiCtx`→`EguiUiCtx` rename fixed; iced threading updated to use `IcedConfig`/`apply_message`/`WidgetState`; private `OxiIcedMsg` enum removed, `oxiui_iced::Message` used directly. `AppConfig` + `AppExit` added. Lifecycle hooks (`on_init`, `on_frame`) added; Plugin trait + priority-ordered registry added; `HotkeyRegistry` with conflict detection added; `CommandPalette` with fuzzy matching added; `NotificationQueue` FIFO added; `prelude` + `core` re-export modules added. All examples updated to `App::new(AppConfig::new().title(...)).run()?`. 24 tests pass, 0 clippy warnings.
- **Deferred:** dialog API (no rfd), system tray/native menu, logging/tracing dep, state persistence (oxicode), App::with_state, multi-window, screenshot API, App::run_with_return<T>, on_close/on_resize hooks, per-frame dirty flag.
- [x] Multi-window support: `App::open_window(WindowConfig)` returning `WindowId`, per-window content closures, cross-window communication channel (~200 SLOC)
- **Completed 2026-06-03:** `src/multiwindow.rs` — `WindowRegistry` wraps `oxiui_core::WindowManager`; `App::open_window(WindowConfig) -> WindowId`, `App::close_window(WindowId)`, `App::secondary_windows() -> &[SecondaryWindow]`, `App::window_channel() -> &WindowChannel`. 8 unit tests in multiwindow.rs; 4 integration tests in app_tests.rs. Cross-window messaging via `WindowChannel::send`/`drain_messages`. Backend dispatch of secondary windows (egui `show_viewport_deferred`, iced multi-window) deferred to M7 — descriptors are queued for backends to consume.
- **Files:** new `crates/oxiui/src/multiwindow.rs`; `crates/oxiui/src/lib.rs` (module + App methods).
- [x] **Facade: iced-path lifecycle/plugin wiring, 7 window-config props, notify/hotkey/command-palette APIs, screenshot, run_with_return** (completed 2026-05-29)
- **Goal:** finish the app shell — wire egui-only round-2 machinery into the iced path, surface built-but-unexposed registries through ergonomic App APIs.
- **Completed:** Step 1 — OxiIcedState extended with on_init/on_frame/plugins/initialised fields (Cell<bool> for interior-mutable init guard); view() now fires init hooks + plugin.init once, then on_frame + plugin.update every frame (mirroring OxiEguiApp). Step 2 — AppConfig extended with 7 new fields: min_size, max_size, decorations(true), transparent(false), always_on_top(false), icon(bytes), position; App builder methods added for all; all 7 wired into egui::ViewportBuilder (with_min_inner_size, with_max_inner_size, with_decorations, with_transparent, with_always_on_top, with_position); icon bytes stored (decoding deferred — no png dep in facade). Step 3 — App::notify(title,body,urgency); App::try_hotkey returning Result<App,HotkeyConflict>; App::register_command + command_matches; App::screenshot() using render-soft + temp-file PNG via RgbaBuffer::save_png; App::run_with_return<T>(); AppExit::RequestedByUser + Programmatic variants; on_close/on_resize/on_focus lifecycle hooks; oxiui::render mod; oxiui::text mod; prelude extended. All examples compile unchanged (use App::new(AppConfig::new()...) API). 16 new tests, 41 total passing, 0 clippy warnings, 1651 SLOC (under 2000).
- **Deviation:** icon bytes stored but not decoded into egui::IconData — png crate is only a dep of render-soft, not oxiui; adding it would be a new dep violating the policy. Icon decoding deferred until a rendering-layer escape hatch exists.
- **Deferred:** dialog API (rfd), system tray/native menu, logging (tracing dep), state persistence (oxicode), App::with_state, multi-window, per-frame dirty flag.
- [x] App state management: `App::with_state(state)` where state is `Send + 'static`, state passed to content closure as `|ui, state|`, automatic persistence via oxicode serialization (~120 SLOC)
- **Goal:** `App::with_state<State:Send+'static>(self, state:State, content:impl FnMut(&mut dyn UiCtx,&mut State)+Send+'static)->Self`; state lives inside the content closure — no Arc/Mutex needed (planned 2026-05-29)
- **Completed 2026-05-29:** `App::with_state` implemented; state captured by value in a move closure; no Arc/Mutex needed; replaces content field; 3 tests pass (builds, runs headless, accumulates state). State persistence (oxicode) stays deferred.
- **Files:** `crates/oxiui/src/lib.rs`
- [x] Plugin system: `Plugin` trait with `fn init(&mut self, ctx: &mut AppCtx)`, `fn update(&mut self, ctx: &mut AppCtx)`, `App::plugin(p)` registration, plugin ordering/priority (~150 SLOC)
- **Goal:** Confirm Plugin system is fully shipped and flip this marker to `[x]`.
- **Design:** `Plugin` trait at `oxiui/src/lib.rs:409-418`; `App::plugin()` at `:1054`; priority-ordered init/update at `:1306`. Implementation subagent to read and verify, then flip `[~]` → `[x]`.
- **Files:** `crates/oxiui/src/lib.rs` (TODO marker only).
- **Tests:** Existing plugin tests pass.
- **Risk:** None — stale marker flip only.
- [x] Backend abstraction: `BackendRunner` trait that egui/iced/slint/dioxus backends implement, `App::run()` dispatches to the selected backend's runner, unified error handling (~100 SLOC)
- **Completed 2026-05-29:** `BackendRunner` trait + `LifecycleConfig` in new `crates/oxiui/src/runner.rs`; `EguiRunner` and `IcedRunner` stubs implemented (live dispatch remains in `run_egui_or_fallback`/`run_iced` for M6 full wiring); cfg-gated re-exports in lib.rs; 3 tests pass. lib.rs at 1726 lines (under 2000, no splitrs needed).
- **Files:** `crates/oxiui/src/runner.rs` (new), `crates/oxiui/src/lib.rs`
- [x] System tray integration: `App::with_tray(TrayConfig)` for background apps, tray icon, tray menu, click-to-show/hide window (~80 SLOC)
- **Completed 2026-06-03:** `src/tray.rs` — `TrayConfig` struct (icon_path, icon_bytes, tooltip, menu_items), `TrayMenuItem` enum (Action/Separator/SubMenu), `TrayHandle::mount(TrayConfig)` creates the OS tray icon when `tray` feature enabled (via `tray-icon 0.24` crate). `App::with_tray(TrayConfig) -> Result<Self, String>` builder. 8 unit tests pass (config builder, handle mount no-op without feature). With `tray` feature: menu-click callbacks are stored at data-model level; full event-loop integration (callbacks firing during eframe loop) is planned for a future release (basic implementation).
- **Files:** new `crates/oxiui/src/tray.rs`; `crates/oxiui/src/lib.rs` (module + App method + pub use).
- [x] Native menu bar: `App::menu_bar(|menu| { menu.item("File").submenu(|sub| { sub.item("Open").on_click(||) }) })` cross-platform menu builder (~100 SLOC)
- **Completed 2026-06-03:** `src/menu.rs` — `MenuBar::build(|mb| {...})` closure DSL; `Menu` with `item`, `separator`, `submenu`; `MenuItem` enum (Action/Separator/Submenu); `App::menu_bar(F)` and `App::with_menu_bar(MenuBar)` builders; `App::get_menu_bar() -> Option<&MenuBar>`. 8 unit tests in menu.rs; 5 integration tests in app_tests.rs. Backend translation (egui `egui-menu` / iced widget::menu) deferred to M7 — the data model is fully defined and tested.
- **Files:** new `crates/oxiui/src/menu.rs`; `crates/oxiui/src/lib.rs` (module + App methods + pub use).
- [x] Dialog API: `App::file_dialog()` (open/save), `App::message_dialog()` (alert/confirm/prompt), using rfd (Rust File Dialog) or custom impl (~80 SLOC)
- **Completed 2026-06-03:** `src/dialog.rs` — pure in-process `DialogQueue` with `request(DialogKind) -> DialogId`, `pop_pending`, `respond(id, DialogResponse)`, `pop_response(id)`, `peek_response(id)`; `DialogKind` enum (Alert/Confirm/Prompt/FileOpen/FileSave); `DialogResponse` enum (Dismissed/Confirmed/Cancelled/Text/FilePaths/SavePath). `App::message_dialog`, `App::confirm_dialog`, `App::prompt_dialog`, `App::file_dialog`, `App::file_save_dialog`, `App::poll_dialog`, `App::respond_dialog`, `App::dialog_queue()`. 9 unit tests in dialog.rs; 6 integration tests in app_tests.rs.
- **Updated 2026-06-03:** `src/native_dialog.rs` — `dialogs` Cargo feature (backed by `rfd 0.17.2`) adds `App::file_dialog_native`, `App::message_dialog_native`; `open_file_dialog`, `save_file_dialog`, `message_dialog`, `confirm_dialog` blocking helpers. 8 unit tests pass (ignored under `dialogs` feature on macOS/Windows since rfd requires main thread).
- **Files:** new `crates/oxiui/src/dialog.rs`, `crates/oxiui/src/native_dialog.rs`; `crates/oxiui/src/lib.rs` (module + App methods + pub use).
- [x] Command palette: searchable command list, `App::register_command("name", shortcut)` + `App::command_matches(query)`, fuzzy matching
- [x] Hotkey registration: `App::try_hotkey(mods, key, action)` → `Result<App, HotkeyConflict>`, conflict detection via HotkeyRegistry
- [x] Theme selector UI: built-in theme picker widget accessible via menu or shortcut, preview before apply (~40 SLOC)
- **Completed 2026-05-29 (S4):** New `src/theme_picker.rs`. `pub fn theme_picker(ui: &mut dyn UiCtx, current: &mut &'static str) -> bool` renders one button per built-in theme; returns `true` if selection changed. `pub fn by_name(name: &str) -> Box<dyn Theme>` converts names to theme objects. `BUILTIN_THEMES` constant. Re-exported at crate root as `theme_picker`, `theme_by_name`, `BUILTIN_THEMES`. 4 unit tests pass.
- **Deviation:** Uses `ui.button()` per theme (no dropdown, since `UiCtx::dropdown` returns `DropdownResponse::unsupported()` in most backends). Fallback approach works with all UiCtx implementations.
- **Files:** new `crates/oxiui/src/theme_picker.rs`; `crates/oxiui/src/lib.rs` (`pub mod theme_picker;` + re-exports).
- [x] Notification system: `App::notify(title, body, urgency)` for in-app toast notifications, urgency-driven TTL (3/5/10 s)
- [x] Logging integration: `tracing` subscriber setup for UI events, performance tracing spans for frame/layout/paint phases (~40 SLOC)
- **Completed 2026-06-03:** `src/logging.rs` with `init_logging(LogLevel)`, `init_with_config(LoggingConfig)`, `LoggingConfig` builder (no_ansi, with_file, with_thread_ids), `INIT: OnceLock` guard for idempotent install, `frame_span!/layout_span!/paint_span!/event_span!` macros. 7 tests pass. Gated on `tracing` feature.
- [x] Screenshot API: `App::screenshot() -> Result<Vec<u8>, UiError>` — PNG bytes via render-soft headless path (software feature)
- [x] App icon and metadata: icon bytes accepted via AppConfig::icon(bytes) / App::icon(bytes); decoding to egui::IconData deferred (no png dep in facade)
- **Completed 2026-05-29 (S4):** New `src/icon.rs`. `pub(crate) fn decode_icon(bytes: &[u8]) -> Result<egui::IconData, UiError>` decodes PNG → RGBA8 → `egui::IconData{rgba, width, height}`. Supports RGBA8/RGB8/Grayscale/GrayscaleAlpha color types. Wired into `run_egui_or_fallback` — decode errors are non-fatal (logs to stderr, continues without icon). Gated `#[cfg(feature = "egui")]` (png is a guaranteed transitive dep when egui is enabled). 2 unit tests in icon.rs (encode+decode a 4×4 RGBA PNG; invalid bytes → Err).
- **Deviation:** Gated on `egui` feature only (not a separate `png` feature) because `dep:png` is pulled in as a non-feature dep by the `egui` feature group. `eframe::IconData` does not exist; type is `egui::IconData`.
- **Files:** new `crates/oxiui/src/icon.rs`; `crates/oxiui/src/lib.rs` (wiring in `run_egui_or_fallback`).
## API Improvements
- [x] `App::run()` returns `Result<AppExit, UiError>` with structured exit reason; AppExit now has Ok/Error/RequestedByUser/Programmatic(String) variants
- [x] `App::new()` accepts `AppConfig` struct with builder for all window/backend/theme configuration in one place
- [x] Re-export all subcrate types under organized module paths: `oxiui::core::*`, `oxiui::text::*`, `oxiui::render::*`
- [x] Feature flag documentation: `#[doc(cfg(feature = "..."))]` on all feature-gated modules for docs.rs visibility
- **Completed 2026-05-29 (S4):** Added `#![cfg_attr(docsrs, feature(doc_cfg))]` at crate level. Added `#[cfg_attr(docsrs, doc(cfg(feature="...")))]` to `pub mod table` (table), `pub mod accessibility` (a11y), `pub mod recording` (a11y), `pub mod web` (web), `pub mod render` (software), and gated re-exports `EguiRunner` (egui), `IcedRunner` (iced), recording re-exports (a11y). Runner module itself is not feature-gated.
- **Files:** `crates/oxiui/src/lib.rs`.
- [x] Prelude module: `oxiui::prelude` re-exporting App, AppConfig, AppExit, Backend, Plugin, HotkeyConflict, Notification + core types
- [x] `App::run_with_return<T>()` — headless-path returns closure value directly; real-backend path returns Err(Unsupported)
## Testing
- [x] Headless smoke test: `App::new("test").content(|ui| { ui.heading("H"); ui.label("L"); ui.button("B"); }).run_headless_once()` succeeds for all backends (~30 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_headless_smoke_all_widgets` in `tests/app_tests.rs`.
- [x] Backend selection test: `Backend::Egui` runs headless, `Backend::Iced` runs headless (both feature-gated) (~20 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_backend_default_is_egui` + `test_backend_egui_headless` (egui-gated) in `tests/app_tests.rs`.
- [x] Theme application test: set custom theme, run headless, verify theme was applied (mock UiCtx records palette) (~30 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_theme_application_dark` + `test_theme_application_cooljapan` in `tests/app_tests.rs`.
- [x] Window configuration test: set window size/title, verify configuration propagated to backend (~20 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_window_config_title_stored` + `test_window_config_size_stored` in `tests/app_tests.rs`.
- [x] State management test: init state, modify in content closure, verify state persisted across frames (~40 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_state_management_increments` in `tests/app_tests.rs`.
- [x] Plugin test: register plugin, verify `init` and `update` called in correct order (~30 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_plugin_ordering_by_priority` (OrderPlugin struct) in `tests/app_tests.rs`.
- [x] Feature gate tests: compile with each feature combination (`egui`, `iced`, `table`, `a11y`, `web`, `software`), verify no compilation errors (~0 SLOC, CI matrix)
- **Completed 2026-06-03:** `tests/feature_gates.rs` — 6 tests each spawn `cargo check -p oxiui --features <combo>` and assert success. Covers: default, tracing, persist, table, a11y, software. Must run with `--test-threads 1` to avoid Cargo file-lock contention. All 6 pass.
- **Files:** `crates/oxiui/tests/feature_gates.rs`.
- [x] Example compilation test: all examples in `examples/` compile with their required features (~0 SLOC, CI gate)
- **Completed 2026-06-03:** `tests/example_compilation.rs` — 3 tests spawn `cargo build -p oxiui --example <name>` and assert success. Covers: hello (default), hello_iced (iced), hello_table (table). Must run with `--test-threads 1`. All 3 pass.
- **Files:** `crates/oxiui/tests/example_compilation.rs`.
- [x] Integration test: full app lifecycle (init → 3 frames → close), verify no leak or panic (~40 SLOC)
- **Completed 2026-05-29 (SA-facade-tests):** `test_full_lifecycle_init_frames_close` in `tests/app_tests.rs`.
## Performance
- [x] Lazy backend initialization: defer GPU/window creation until `run()` is called, not at `App::new()`
- **Completed 2026-05-29 (S4):** Confirmed: `App::new(config)` only stores `config`; `run()` calls `eframe::run_native`/`iced::application` only on invocation. `run_headless_once` never opens a window. Existing tests (`headless_once_returns_app_exit_ok`, `test_frame_skip_default_false`) verify builder + headless path are callable without window creation.
- [x] Frame skipping: if content closure signals no changes, skip the paint phase (dirty-flag optimization)
- **Completed 2026-05-29 (S4):** Added `App::with_frame_skip(bool)` builder. `OxiEguiApp` checks `egui_ctx.input(|i| i.events.is_empty())` each frame; if `frame_skip=true` and no events, calls `ctx.request_repaint_after(Duration::from_secs(1))` to yield CPU. Default is `false` (no behaviour change). 2 tests: builder compiles, headless run with `frame_skip=false` works.
- **Files:** `crates/oxiui/src/lib.rs`.
- [x] Startup time measurement: log time from `run()` to first frame rendered, target < 200ms on desktop
- **Completed 2026-06-03:** `App::startup_clock() -> std::time::Instant` captures the wall-clock instant at the call site. Apps call it just before `app.run()` and compare with the `Instant` from the first `on_init` hook to measure startup latency. 1 test (monotonicity check) passes. Full in-framework timing (eframe first-frame callback) is deferred pending eframe API stabilisation.
- **Files:** `crates/oxiui/src/lib.rs` (App::startup_clock).
- [x] Memory usage baseline: measure RSS after 1000 frames with a 100-widget UI, target < 50MB
- **Completed 2026-06-03:** `oxiui::process_rss_bytes() -> Option<u64>` returns current process RSS in bytes. On Linux reads `/proc/self/status` (VmRSS); on macOS/other returns `None` (no Pure Rust path to `getrusage`/`task_info`). 1 test (panics-never check) passes. Full 1000-frame benchmark is a criterion bench task.
- **Files:** `crates/oxiui/src/lib.rs` (process_rss_bytes).
## Integration
- [x] `oxiui-core` integration: facade should re-export all core types; expanded `UiCtx` methods available through the facade
- **Completed 2026-05-29 (S4):** Confirmed all core types re-exported via `pub mod core { pub use oxiui_core::*; }`, `pub mod solver`, `pub mod text`, `pub mod reactive` (Signal/Computed/ReactiveRuntime/ReactiveError). Reactive primitives added to prelude. Stale marker flipped.
- **Files:** `crates/oxiui/src/lib.rs` (TODO marker only).
- [x] `oxiui-text` integration: `App::with_font(bytes)` loads font into the active backend's text system
- **Completed 2026-05-29:** `App::with_font(family_name, bytes)` implemented; stores in `AppConfig::extra_fonts: Vec<(String,Vec<u8>)>`; egui path calls `oxiui_egui::load_fonts_into_egui` at startup; `App::extra_fonts()` accessor added for testing; 2 tests pass. iced font loading deferred (iced `advanced` feature required).
- **Deviation:** `oxiui::text::TextArea`/`WrapMode` re-export deferred (oxiui-text is NOT a dep of oxiui, adding it would violate zero-new-deps policy); `oxiui::solver` module deferred (Solver/Variable/etc. types do not exist in oxiui-core). The existing `oxiui::text` module exposes `FontSpec`/`FontStyle`/`FontFeature` from oxiui-core.
- **Files:** `crates/oxiui/src/lib.rs`
- [x] `oxiui-theme` integration: `App::theme(t)` should accept `DesignTokens` and `TypographyScale`, not just `Palette`+`FontSpec`
- **Completed 2026-06-03:** `App::with_design_tokens(DesignTokens)` and `App::with_typography(TypographyScale)` store tokens in new `AppConfig::design_tokens` and `AppConfig::typography` fields. `App::design_tokens()` and `App::typography()` accessors with graceful fallback to defaults. 4 tests pass. Blanket-impl conflict on `ThemeExt` resolved by storing tokens in config rather than wrapping the theme.
- **Deviation:** Cannot wrap the theme in a custom `ThemeExt` impl due to oxiui-theme's blanket `impl<T: Theme> ThemeExt for T`. Tokens stored in AppConfig and accessible to backends that need the extended token set.
- [x] `oxiui-render-wgpu` / `oxiui-render-soft` integration: `App::renderer()` exposes the active render backend for advanced users (custom draw commands)
- **Completed 2026-06-03:** `App::soft_renderer() -> SoftRenderer` (requires `software` feature) creates a software renderer instance for off-screen use. Full wgpu renderer access (requires opened device) is deferred; the egui backend's wgpu device is owned by eframe and not accessible via a single method.
- **Files:** `crates/oxiui/src/lib.rs` (App::soft_renderer).
- [x] `oxiui-egui` / `oxiui-iced` integration: backend-specific escape hatches (`App::with_egui_ctx(|egui_ctx|)`, `App::with_iced_state(|state|)`) for advanced customization
- **Completed 2026-05-29 (S4):** `App::with_egui_ctx(f: impl FnMut(&egui::Context) + Send + 'static)` added; stores as `Vec<EguiFrameHook>` (type alias to avoid clippy type_complexity lint); called in `OxiEguiApp::ui()` on each frame after content+plugins. `with_icon(Arc::new(icon_data))` also passes egui context through. Gated `#[cfg(feature="egui")]`. iced escape hatch deferred (iced's retained-mode update model differs significantly).
- **Files:** `crates/oxiui/src/lib.rs`.
- [x] `oxiui-table` integration: `App::table(source)` convenience method that renders a full-window table app
- **Completed 2026-05-29 (S4):** `App::table<S: RowSource + Send + 'static>(source: S) -> Self` added; wraps source in `Arc<Mutex<S>>`; renders headers and cells via `UiCtx::label`. Gated `#[cfg(feature="table")]`. 2 tests pass (compiles, headless run).
- **Deviation:** `oxiui_table::Table::render_egui` requires `&mut egui::Ui` (not `&mut dyn UiCtx`); cannot be called from a backend-agnostic content closure. Backend-agnostic table rendering uses `RowSource::column_defs`/`row` iterated through `ui.label`. Full egui table features (sorting/filtering/resizing) require direct use of `oxiui_table::Table` inside an egui content closure.
- **Files:** `crates/oxiui/src/lib.rs`.
- [x] `oxiui-accessibility` integration: automatic a11y tree generation from the widget tree, pushed to platform adapter each frame
- **Goal:** `RecordingUiCtx` in new `recording.rs` — records widget calls as `RecordingEntry{role:WidgetRole,label}` during content execution; `build_a11y_tree(root_id)->A11yTree` builds one A11yNode per entry; `App::build_a11y_snapshot(&mut self)->A11yTree` runs content once via RecordingUiCtx (headless) (planned 2026-05-29)
- **Design:** `RecordingUiCtx{delegate:Option<&mut dyn UiCtx>,entries:Vec<RecordingEntry>}` implements `UiCtx`: heading→WidgetRole::Heading, label→StaticText, button→Button (records + pass-through to delegate if Some); 7 extension methods (horizontal/vertical/grid/etc.) call content and capture child entries under a Group entry; `build_a11y_tree` uses `A11yNodeBuilder` for each entry, links as children of a synthetic root node; NOTE: platform-adapter push (live winit) stays deferred — RecordingUiCtx is headless-only
- **Files:** new `crates/oxiui/src/recording.rs`; `crates/oxiui/src/lib.rs` (App::build_a11y_snapshot + `pub mod recording` re-export)
- **Tests:** RecordingUiCtx records heading+button in order; build_a11y_tree node count matches entries; with_delegate passes through to real UiCtx; horizontal content's child entries captured under Group; build_a11y_snapshot via App compiles and returns tree
- **Risk:** RecordingUiCtx must implement the 7 new UiCtx extension methods (Stage 2 depends on Stage 1/A landing them); run recording.rs only after Stage 1 is green; WidgetRole lives in oxiui-accessibility (not core) — check oxiui facade's Cargo.toml has oxiui-accessibility dep
- [~] `oxiui-web` integration: `App::run()` on wasm32 target should auto-detect and use `oxiui-web::mount()`
- **Goal:** On `wasm32`, `App::run()` automatically routes to `oxiui_web::mount()` instead of returning `Err(Unsupported)`.
- **Status (S4):** The `#[cfg(all(feature = "egui", target_arch = "wasm32"))]` branch of `run_egui_or_fallback` exists and correctly returns `Err(UiError::Unsupported)` directing users to `oxiui_web::mount()`. The `web` feature module now carries `#[cfg_attr(docsrs, doc(cfg(feature = "web")))]`. Full auto-dispatch (calling mount() directly from App::run()) is deferred: requires wasm32 cross-compilation to verify safely, and `oxiui_web::mount`'s exact signature needs review for the integration point.
- **BLOCKED: Requires wasm32 cross-compilation (`cargo build --target wasm32-unknown-unknown`) to test. The `oxiui_web::mount` signature takes a `HtmlCanvasElement` canvas ID string that must be passed through `AppConfig` — this is an API design decision that needs to be made before auto-dispatch can be wired. Deferred until wasm32 build infra is in place.**
- **Files:** `crates/oxiui/src/lib.rs`.
- [x] COOLJAPAN ecosystem: app state persistence via oxicode (not bincode); asset bundling via oxiarc-* (not zip); no C/C++ dependencies in the facade itself
- **Completed 2026-06-03:** `App::with_persistent_state<State: Encode+Decode+Send+'static>(initial, path, content)` loads state from `path` via `oxicode::decode_value`, runs content, and captures state for persistence. Load errors are non-fatal (falls back to `initial`, warns to stderr). New `persist` feature gates `dep:oxicode`. No C/C++ deps in the facade (verified: `default = ["gpu","egui"]` is Pure Rust). Asset bundling via oxiarc-* is deferred (no asset pipeline in current scope).
- **Files:** `crates/oxiui/Cargo.toml` (persist feature), `crates/oxiui/src/lib.rs` (App::with_persistent_state).