From 4eb052a0b35bd35eccf313297a56174139bd2c97 Mon Sep 17 00:00:00 2001 From: Schell Carl Scivally Date: Wed, 15 Jul 2026 11:32:58 +1200 Subject: [PATCH 1/3] bump mogwai-macros dep --- crates/mogwai/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/mogwai/Cargo.toml b/crates/mogwai/Cargo.toml index a8eec66..40c0948 100644 --- a/crates/mogwai/Cargo.toml +++ b/crates/mogwai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mogwai" -version = "0.7.5" +version = "0.7.6" edition = "2024" authors = ["Schell Scivally "] license = "MIT" @@ -23,7 +23,7 @@ async-channel = { workspace = true, optional = true } futures-core = "0.3.31" futures-lite.workspace = true log.workspace = true -mogwai-macros = { version = "0.2.1", path = "../mogwai-macros" } +mogwai-macros = { version = "0.2.2", path = "../mogwai-macros" } serde.workspace = true wasm-bindgen.workspace = true wasm-bindgen-futures = { workspace = true, optional = true } From bd51a4275fbef2f4f26f09706597266a8a1c242c Mon Sep 17 00:00:00 2001 From: "Schell Carl Scivally with glm-5.2 ollama-cloud/glm-5.2" Date: Sat, 8 Aug 2026 15:37:41 +1200 Subject: [PATCH 2/3] feat: add Step/StepMut/StepWith/StepWithMut traits Formalize the pull-based `step` event-loop convention as four traits in a new `step.rs` module, re-exported from the prelude and `future` module. - Step: immutable borrow, for event-listener-only widgets (concurrent racing) - StepMut: exclusive borrow, for widgets that mutate their own fields - StepWith: container racing per-child futures via closure (&self) - StepWithMut: container racing per-child futures via closure (&mut self) All traits use RPITIT (not object-safe) with Output: 'static. This is a non-breaking addition; existing inherent step methods are unaffected. Closes schell-09d --- crates/mogwai/src/future.rs | 6 ++ crates/mogwai/src/lib.rs | 17 ++-- crates/mogwai/src/step.rs | 192 ++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 crates/mogwai/src/step.rs diff --git a/crates/mogwai/src/future.rs b/crates/mogwai/src/future.rs index 453ee02..0372282 100644 --- a/crates/mogwai/src/future.rs +++ b/crates/mogwai/src/future.rs @@ -1,10 +1,16 @@ //! Utilitites for working with futures. //! //! These are meant to be small additions to [`futures_lite`]. +//! +//! Re-exports the [`step`](crate::step) traits for convenience, since +//! [`StepWith`] / [`StepWithMut`] closures compose naturally with +//! [`race_all`]. use std::{future::Future, pin::Pin}; use futures_lite::FutureExt; +pub use crate::step::{Step, StepMut, StepWith, StepWithMut}; + pub trait MogwaiFutureExt where Self: Sized + Future, diff --git a/crates/mogwai/src/lib.rs b/crates/mogwai/src/lib.rs index 049f4f6..a0d1f08 100644 --- a/crates/mogwai/src/lib.rs +++ b/crates/mogwai/src/lib.rs @@ -1,23 +1,26 @@ //! # Mogwai: Cross-Platform UI Library //! -//! Mogwai is a Rust library for building UI components that work across platforms, -//! but primarily in the browser. +//! Mogwai is a Rust library for building UI components that work across +//! platforms, but primarily in the browser. //! //! ## Key Concepts //! -//! - **Low boilerplate view construction**: Use the [`rsx!`](view::rsx) macro to reduce boilerplate. +//! - **Low boilerplate view construction**: Use the [`rsx!`](view::rsx) macro +//! to reduce boilerplate. //! - **Async event handling**: Events are futures, not callbacks. -//! - **Cross-platform support**: [View traits](crate::view) ensure operations are cross-platform, -//! with room for specialization. +//! - **Cross-platform support**: [View traits](crate::view) ensure operations +//! are cross-platform, with room for specialization. //! - **Idiomatic Rust**: Widgets are Rust types //! -//! Mogwai provides tools to implement these concepts efficiently, promoting flexibility and performance. +//! Mogwai provides tools to implement these concepts efficiently, promoting +//! flexibility and performance. pub mod an_introduction; #[cfg(feature = "future")] pub mod future; pub mod proxy; #[cfg(feature = "ssr")] pub mod ssr; +pub mod step; mod str; pub mod sync; pub mod time; @@ -27,7 +30,7 @@ pub mod web; pub mod prelude { //! Common prelude between all platforms. - pub use crate::{proxy::*, str::*, view::*}; + pub use crate::{proxy::*, step::*, str::*, view::*}; } pub use str::Str; diff --git a/crates/mogwai/src/step.rs b/crates/mogwai/src/step.rs new file mode 100644 index 0000000..7c9e22a --- /dev/null +++ b/crates/mogwai/src/step.rs @@ -0,0 +1,192 @@ +//! Pull-based stepping traits for widget event loops. +//! +//! Mogwai widgets are driven by a pull-based event loop: a caller awaits the +//! widget's next event, reacts to it, then awaits again. This module +//! formalizes that convention as four traits so the compiler — not prose — +//! enforces the contract and so generic composition (e.g. racing N children) +//! becomes possible. +//! +//! ## When to use which trait +//! +//! | Trait | Receiver | Use when | +//! |------|----------|----------| +//! | [`Step`] | `&self` | `step` only awaits event listeners (interior mutability). Lets a parent race multiple children concurrently. | +//! | [`StepMut`] | `&mut self` | `step` mutates the widget's own fields or drives a mutable resource. Children cannot be raced concurrently. | +//! | [`StepWith`] | `&self` | A container of `T`-typed children that races a per-child future (supplied by a closure) against its own event. | +//! | [`StepWithMut`] | `&mut self` | Same, but with exclusive access to each child. | +//! +//! ## Object safety +//! +//! These traits use `impl Future` returns (RPITIT) and are therefore **not** +//! object-safe. If a `dyn Step` need ever arises, add a boxed companion trait +//! with a blanket bridge as a non-breaking addition. + +use std::{future::Future, pin::Pin}; + +/// Pull-based event source — immutable borrow. +/// +/// Implement this when `step` only awaits event listeners (which use interior +/// mutability). This lets a parent race multiple children's `step()` futures +/// concurrently without borrow conflicts. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{prelude::*, step::Step}; +/// +/// struct Button { +/// on_click: V::EventListener, +/// } +/// +/// impl Step for Button { +/// type Output = V::Event; +/// fn step(&self) -> impl Future { +/// self.on_click.next() +/// } +/// } +/// ``` +pub trait Step { + /// The event produced by a single step. Must be `'static` so any `Step` + /// can be plugged into a [`StepWith`] closure. + type Output: 'static; + + /// Advance the widget by one event. May resolve to `Self::Output` or + /// never resolve (e.g. a presentational component with no events). + fn step(&self) -> impl Future; +} + +/// Pull-based event source — exclusive borrow. +/// +/// Implement this when `step` mutates the widget's own fields or drives a +/// mutable resource it owns. A parent cannot race two `StepMut` children +/// concurrently; use [`Step`] instead when concurrent racing is needed. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{prelude::*, step::StepMut}; +/// +/// struct Counter { +/// count: u32, +/// on_click: ::EventListener, +/// } +/// +/// impl StepMut for Counter { +/// type Output = (); +/// fn step_mut(&mut self) -> impl Future { +/// async move { +/// let _ev = self.on_click.next().await; +/// self.count += 1; +/// } +/// } +/// } +/// ``` +pub trait StepMut { + /// The event produced by a single step. Must be `'static` so any + /// `StepMut` can be plugged into a [`StepWithMut`] closure. + type Output: 'static; + + /// Advance the widget by one event, mutating internal state. + fn step_mut(&mut self) -> impl Future; +} + +/// A container of `T`-typed children that races a per-child future (supplied by +/// a closure) against its own event future — immutable borrow. +/// +/// This generalizes the `List::step` / `ButtonGroup::step` / `TabList::step` +/// pattern: the container owns `N` children of type `T`, and the caller +/// decides how each child produces a future of type `Ev`. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{ +/// prelude::*, +/// step::{Step, StepWith}, +/// }; +/// use std::pin::Pin; +/// +/// struct List { +/// items: Vec, +/// // ... +/// # _phantom: std::marker::PhantomData, +/// } +/// +/// impl StepWith for List { +/// type Output = (); +/// fn step_with( +/// &self, +/// f: impl FnMut(&T) -> Pin + '_>>, +/// ) -> impl Future +/// where +/// Ev: 'static, +/// { +/// async move { +/// // race all children's futures... +/// # std::future::pending::<()>().await +/// } +/// } +/// } +/// ``` +pub trait StepWith { + /// The event produced by the container's own step. + type Output: 'static; + + /// Race the container's own event future against a future produced by + /// `f` for each child. The first to resolve wins. + fn step_with( + &self, + f: impl FnMut(&T) -> Pin + '_>>, + ) -> impl Future + where + Ev: 'static; +} + +/// A container of `T`-typed children that races a per-child future (supplied by +/// a closure) against its own event future — mutable borrow. +/// +/// This generalizes the `TabPanel::step_with` / `Table::step_with` pattern: the +/// container owns `N` children of type `T` mutably, and the caller decides how +/// each produces a future of type `Ev`. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{prelude::*, step::StepWithMut}; +/// use std::pin::Pin; +/// +/// struct TabPanel { +/// panes: Vec

, +/// // ... +/// # _phantom: std::marker::PhantomData, +/// } +/// +/// impl StepWithMut

for TabPanel { +/// type Output = (); +/// fn step_with_mut( +/// &mut self, +/// f: impl FnMut(&mut P) -> Pin + '_>>, +/// ) -> impl Future +/// where +/// Ev: 'static, +/// { +/// async move { +/// // race tab clicks against all pane futures... +/// # std::future::pending::<()>().await +/// } +/// } +/// } +/// ``` +pub trait StepWithMut { + /// The event produced by the container's own step. + type Output: 'static; + + /// Race the container's own event future against a future produced by + /// `f` for each child (with mutable access). The first to resolve wins. + fn step_with_mut( + &mut self, + f: impl FnMut(&mut T) -> Pin + '_>>, + ) -> impl Future + where + Ev: 'static; +} From b6f460aa932f4122ac2e215d65c6841cf6ca8ea4 Mon Sep 17 00:00:00 2001 From: "Schell Carl Scivally with glm-5.2 ollama-cloud/glm-5.2" Date: Sat, 8 Aug 2026 15:50:15 +1200 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20address=20Copilot=20review=20?= =?UTF-8?q?=E2=80=94=20HRTB=20lifetimes,=20doc=20clarification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StepWith/StepWithMut: use for<'a> HRTB so returned futures can borrow from the child reference for exactly as long as that borrow lives. - future.rs: clarify race_all requires 'static output (Ev: 'static) even though the future itself may borrow from its child via the HRTB. - Update doc examples to match the new HRTB signatures. --- crates/mogwai/src/future.rs | 9 ++++++--- crates/mogwai/src/step.rs | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/mogwai/src/future.rs b/crates/mogwai/src/future.rs index 0372282..d699a96 100644 --- a/crates/mogwai/src/future.rs +++ b/crates/mogwai/src/future.rs @@ -2,9 +2,12 @@ //! //! These are meant to be small additions to [`futures_lite`]. //! -//! Re-exports the [`step`](crate::step) traits for convenience, since -//! [`StepWith`] / [`StepWithMut`] closures compose naturally with -//! [`race_all`]. +//! Re-exports the [`step`](crate::step) traits for convenience. +//! Note: [`race_all`] boxes futures as `Pin>>` +//! (default `'static`), so per-child futures passed to [`StepWith`] / +//! [`StepWithMut`] closures must produce a `'static`-compatible output +//! (`Ev: 'static`) even though the future itself may borrow from its +//! child reference via the higher-ranked lifetime. use std::{future::Future, pin::Pin}; use futures_lite::FutureExt; diff --git a/crates/mogwai/src/step.rs b/crates/mogwai/src/step.rs index 7c9e22a..edebb82 100644 --- a/crates/mogwai/src/step.rs +++ b/crates/mogwai/src/step.rs @@ -116,7 +116,7 @@ pub trait StepMut { /// type Output = (); /// fn step_with( /// &self, -/// f: impl FnMut(&T) -> Pin + '_>>, +/// f: impl for<'a> FnMut(&'a T) -> Pin + 'a>>, /// ) -> impl Future /// where /// Ev: 'static, @@ -134,9 +134,14 @@ pub trait StepWith { /// Race the container's own event future against a future produced by /// `f` for each child. The first to resolve wins. + /// + /// The closure uses a higher-ranked trait bound (`for<'a>`) so the returned + /// boxed future is allowed to borrow from the child reference for exactly + /// as long as that borrow lives — e.g. `Box::pin(child.step())` where + /// `step` borrows `&'a self`. fn step_with( &self, - f: impl FnMut(&T) -> Pin + '_>>, + f: impl for<'a> FnMut(&'a T) -> Pin + 'a>>, ) -> impl Future where Ev: 'static; @@ -165,7 +170,7 @@ pub trait StepWith { /// type Output = (); /// fn step_with_mut( /// &mut self, -/// f: impl FnMut(&mut P) -> Pin + '_>>, +/// f: impl for<'a> FnMut(&'a mut P) -> Pin + 'a>>, /// ) -> impl Future /// where /// Ev: 'static, @@ -183,9 +188,14 @@ pub trait StepWithMut { /// Race the container's own event future against a future produced by /// `f` for each child (with mutable access). The first to resolve wins. + /// + /// The closure uses a higher-ranked trait bound (`for<'a>`) so the returned + /// boxed future is allowed to borrow from the child reference for exactly + /// as long as that borrow lives — e.g. `Box::pin(child.step_mut())` where + /// `step_mut` borrows `&'a mut self`. fn step_with_mut( &mut self, - f: impl FnMut(&mut T) -> Pin + '_>>, + f: impl for<'a> FnMut(&'a mut T) -> Pin + 'a>>, ) -> impl Future where Ev: 'static;