Menu
Public repository / master
AkurAI-Framework / crates/ui/src/lib.rs
Latest change d7f31233a0aaba65d741a85ac631a0e9004dba49 - akurai-ui kit: chat components (thread, message, tool_call, system_line, composer, presence) with Normal/Tv scale by Ólafur Búi Ólafsson
//! AkurAI-Framework UI — a pure-Rust, React-style reactive UI for the browser.
//!
//! Components are Rust functions; state is fine-grained [`signal`]s; markup is
//! a chainable element API (`div().class("flex gap-2").child(…)`) styled with
//! the framework's utility CSS engine (`akurai-css`, Tailwind-class). The app
//! compiles to `wasm32-unknown-unknown` with **no wasm-bindgen and no external
//! crates**: [`host.js`](HOST_JS), a ~150-line shim, is the entire JavaScript.
//!
//! - [`reactive`] — signals, effects, memos, scopes, context.
//! - [`dom`] — views, elements, dynamic regions, keyed lists, portals.
//! - [`kit`] — accessible components in the spirit of Radix + shadcn/ui
//! (button, card, dialog, tabs, checkbox, toast, …), flat-cornered.
//! - [`router`], [`http`], [`platform`] — hash routing, fetch, timers.
//! - [`testing`] — an in-memory DOM so components are tested with `cargo test`.
//!
//! An app crate is a `cdylib` that calls [`app!`] with its root component:
//!
//! ```ignore
//! fn root() -> impl akurai_ui::IntoView { "hello" }
//! akurai_ui::app!(root);
//! ```
mod callbacks;
pub mod dom;
mod host;
pub mod html;
pub mod http;
pub mod kit;
pub mod platform;
pub mod reactive;
pub mod router;
#[cfg(not(target_arch = "wasm32"))]
pub mod testing;
pub use callbacks::live as live_callbacks;
pub use dom::listen;
pub use dom::{each, show, when, El, Event, IntoView, NodeRef, View};
pub use host::{Node, BODY, DOCUMENT, WINDOW};
pub use reactive::{batch, effect, memo, signal, untrack, Signal};
/// Everything a component file usually needs.
pub mod prelude {
pub use crate::dom::{dynamic, each, portal, show, when, El, Event, IntoView, NodeRef, View};
pub use crate::html::*;
pub use crate::reactive::{
batch, effect, expect_context, memo, on_cleanup, provide_context, signal, untrack,
use_context, Signal,
};
pub use crate::router::{navigate, use_route};
}
/// The browser host shim. Serve it next to the app's `.wasm`.
pub const HOST_JS: &str = include_str!("host.js");
/// Reset and base element styles the kit assumes. Prepend to the app's CSS.
pub const BASE_CSS: &str = include_str!("base.css");
/// The kit's source, for the utility-CSS scanner: an app's stylesheet must
/// include the classes the kit uses, so builds scan these alongside the app.
pub const STYLE_SOURCES: &[&str] = &[
include_str!("kit/mod.rs"),
include_str!("kit/button.rs"),
include_str!("kit/card.rs"),
include_str!("kit/chat.rs"),
include_str!("kit/form.rs"),
include_str!("kit/dialog.rs"),
include_str!("kit/tabs.rs"),
include_str!("kit/toast.rs"),
include_str!("kit/misc.rs"),
include_str!("kit/icons.rs"),
];
/// Mount `root` into `#app` (or `<body>`), inside a root scope that lives for
/// the page's lifetime. Called by [`app!`].
pub fn start<V: IntoView>(root: impl FnOnce() -> V) {
std::panic::set_hook(Box::new(|info| host::log(&format!("panic: {info}"))));
let target = host::get_by_id("app").unwrap_or(host::BODY);
let ((), scope) = reactive::create_root(|| dom::mount(target, root().into_view(), None));
let _ = scope; // the root scope lives as long as the page
}
/// The wasm ABI entry points `host.js` calls. Not for direct use.
#[doc(hidden)]
pub mod abi {
/// Allocate `len` bytes for JS to write a string into.
pub fn alloc(len: usize) -> *mut u8 {
Box::into_raw(vec![0u8; len].into_boxed_slice()) as *mut u8
}
pub fn dispatch(callback: u32, event: u32) {
crate::callbacks::dispatch(callback, crate::host::EventRef(event));
}
/// # Safety
/// `ptr`/`len` must come from [`alloc`] and be passed exactly once.
pub unsafe fn complete(callback: u32, status: u32, ptr: *mut u8, len: usize) {
// SAFETY: per the contract, this reclaims the box `alloc` leaked.
let bytes = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) };
let body = String::from_utf8(bytes.into_vec()).unwrap_or_default();
crate::callbacks::complete(callback, status as u16, body);
}
}
/// Export the wasm entry points for an app whose root component is `$root`.
#[macro_export]
macro_rules! app {
($root:path) => {
#[no_mangle]
pub extern "C" fn akurai_start() {
$crate::start($root);
}
#[no_mangle]
pub extern "C" fn akurai_alloc(len: usize) -> *mut u8 {
$crate::abi::alloc(len)
}
#[no_mangle]
pub extern "C" fn akurai_dispatch(callback: u32, event: u32) {
$crate::abi::dispatch(callback, event)
}
/// # Safety
/// Called by `host.js` with a buffer from `akurai_alloc`.
#[no_mangle]
pub unsafe extern "C" fn akurai_complete(
callback: u32,
status: u32,
ptr: *mut u8,
len: usize,
) {
unsafe { $crate::abi::complete(callback, status, ptr, len) }
}
};
}