webtest/ai_instructions.md
2025-08-08 08:14:18 +02:00

25 KiB

Yew Framework Documentation for AI Coders: Core Concepts & Coding

This document provides a comprehensive overview of the Yew framework's core coding concepts, designed to equip AI robot coders with the knowledge necessary to build web applications using Rust and WebAssembly.

Yew is a modern Rust framework for building client-side web applications using WebAssembly (Wasm). It enables the development of highly performant web UIs by leveraging Rust's strong type system and rich ecosystem. Yew promotes a component-based architecture for building reusable and maintainable UI elements.

Fundamental Building Blocks

The html! Macro for UI Composition

Yew employs the html! procedural macro for declarative UI construction, drawing inspiration from JSX. This macro is the primary way to define the structure of your component's output.

Syntax and Features:

  • Single Root Node: The html! macro always expects a single root HTML node. To render multiple top-level elements without a wrapping container, use the fragment syntax: <> ... </>.
  • Embedding Rust Expressions: Any valid Rust expression can be embedded within the markup using curly braces ({ expression }). The expression must evaluate to a type that implements Into<Html>.
    let header_text = "Welcome to Yew".to_string();
    let count = 5;
    html! {
        <>
            <h1>{ header_text }</h1>
            <p>{"Current count: "}{ count }</p>
        </>
    }
    
    • Literals: String literals are typically enclosed in quotes and then a {}, e.g., {"Hello"}. They are treated as Text nodes, inherently mitigating common HTML injection (XSS) risks.
  • Element / Component Definition:
    • HTML Elements: Standard HTML elements are written as <tagname property="value">child</tagname> or self-closing <tagname property="value" />.
    • Dynamic Tag Names: For situations where the HTML tag name is determined at runtime, use the @{expression} syntax. The expression must be a string.
      let level = 3;
      html! { <@{format!("h{}", level)} class="subtitle">{"Dynamic Heading"}</@> }
      
    • Yew Components: Yew components are instantiated like custom HTML tags, using PascalCase for their names: <MyComponent property={value} />.
  • Attributes and Properties:
    • HTML Attributes: Set on elements directly: <div attribute={rust_value} />.
    • Boolean Attributes: Set with true or false. false is equivalent to omitting the attribute entirely.
      html! { <input type="checkbox" checked={some_boolean_var} /> }
      
    • String-like Attributes: Can accept &str, String, or Yew's optimized AttrValue (a cheaply cloneable Rc<str> or &'static str). AttrValue is generally recommended for performance-sensitive scenarios, especially when passing values as properties to other components.
    • Optional Attributes: Use Option<T> for attribute values. If the Option is None, the attribute will not be rendered in the DOM.
      let maybe_id: Option<&str> = Some("unique-element");
      html! { <div id={maybe_id}></div> } // Renders with id or not
      
    • Yew-Specific Properties (Special Props): These are not directly reflected in the DOM but serve as instructions to Yew's Virtual DOM.
      • ref={node_ref_handle}: Connects a NodeRef to a DOM element, allowing direct programmatic access to the underlying DOM node (e.g., for canvas manipulation, scrolling, form input values).
      • key={unique_key}: Provides a unique identifier for elements within a list. Crucial for performance optimization, as Yew uses keys to efficiently reconcile list items during updates, preventing unnecessary re-renders or DOM manipulations. Keys must be unique within their immediate siblings (the list itself) and should be stable/deterministic, not based on item position.
  • Conditional Rendering: Use standard Rust if and if let control flow structures directly within the html! macro to conditionally render content.
    let show_message = true;
    html! {
        if show_message {
            <p>{"This message is visible."}</p>
        } else {
            <p>{"Message hidden."}</p>
        }
    }
    
  • List Rendering / Iteration:
    • Use for syntax directly in html!: { for collection.iter() }. This expects the iterator items to be renderable Html.
    • Alternatively, map the collection to Html elements and use .collect::<Html>().
    • Always use key for list items where the order or presence of items can change, as this drastically improves reconciliation performance.
    let items = vec!["Apple", "Banana", "Cherry"];
    html! {
        <ul>
            { for items.iter().map(|item| html! { <li key={item.to_string()}>{item}</li> }) }
        </ul>
    }
    

Components: Function Components

Yew applications are built from components, which encapsulate UI logic and presentation. Function components are the recommended and most common way to define components in modern Yew.

  • Definition: Declare a function component using the #[function_component] attribute on a fn that returns Html. By convention, component names are PascalCase.
    use yew::prelude::*;
    
    #[function_component]
    fn MySimpleComponent() -> Html {
        html! { <p>{"Hello from a component!"}</p> }
    }
    
  • Properties (Props): Data is passed from parent to child components using "props". Props are defined by a struct that must implement Properties (usually via #[derive(Properties)]) and PartialEq. The function component accepts an &Props reference as its single argument.
    #[derive(Properties, PartialEq)]
    pub struct GreetProps {
        pub name: String,
        #[prop_or_default] // Field attribute for optional props with default value
        pub greeting_text: String,
    }
    
    #[function_component]
    fn Greeter(props: &GreetProps) -> Html {
        let greeting = if props.greeting_text.is_empty() {
            "Hello".to_string()
        } else {
            props.greeting_text.clone()
        };
        html! { <p>{ format!("{}, {}!", greeting, props.name) }</p> }
    }
    // Usage: html! { <Greeter name="Alice" /> } or html! { <Greeter name="Bob" greeting_text="Hi" /> }
    
    • Reactive Nature of Props: Yew automatically re-renders a component when its props change (detected via PartialEq).
    • props! Macro: Allows building Properties structs programmatically.
      use yew::props;
      let my_props = props! { GreetProps { name: "Charlie".to_string() } };
      html! { <Greeter ..my_props /> }
      
  • Children (Special Prop): If a component's Props struct includes a pub children: Html field, it can accept nested html! content.
    #[derive(Properties, PartialEq)]
    pub struct CardProps {
        pub title: String,
        pub children: Html, // This field name is special
    }
    
    #[function_component]
    fn Card(props: &CardProps) -> Html {
        html! {
            <div class="card">
                <h2>{ &props.title }</h2>
                <div class="card-content">
                    { props.children.clone() }
                </div>
            </div>
        }
    }
    // Usage: html! { <Card title="My Card"> <p>Some content here.</p> </Card> }
    
  • Generic Components: Function components can be generic over types, provided the generic type parameters meet the necessary trait bounds (e.g., PartialEq).
    #[derive(Properties, PartialEq)]
    pub struct ItemDisplayProps<T: PartialEq + ToHtml> {
        pub item: T,
    }
    
    #[function_component]
    pub fn ItemDisplay<T>(props: &ItemDisplayProps<T>) -> Html
    where
        T: PartialEq + ToHtml + 'static, // 'static for use in VDOM
    {
        html! { <p>{ &props.item }</p> }
    }
    // Usage: html! { <ItemDisplay<i32> item=123 /> }
    
  • Pure Components: A function component is "pure" if its output Html is solely determined by its props, and it has no side effects or internal mutable state. Yew's reconciliation benefits from pure components.
    • Simple pure components with no hooks can sometimes be implemented as regular functions returning Html to reduce overhead.
  • Communication Patterns:
    • Parent to Child: Via Props.
    • Child to Parent: Via Callbacks. The parent passes a Callback to the child via props, and the child calls emit() on it to send data back up.

State Management and Interactivity

Yew provides "hooks" to manage mutable state and side effects within function components.

Hooks

Hooks are functions that allow "hooking into" the lifecycle and state management capabilities of function components.

Rules of Hooks:

  1. Naming Convention: Hook function names must start with use_.
  2. Top-Level Calls Only: Hooks can only be called at the top level of a function component or another hook, and not inside loops, conditionals without if let, or nested functions unless within the scrutinee of a top-level if or match expression.
  3. Consistent Call Order: Hooks must be called in the exact same order on every render. This enables Yew to correctly associate state with hook calls.
  4. No Early Return: A component using hooks cannot return early before all hooks are called unless using Suspense.

Common Pre-defined Hooks:

  • use_state<T>() -> UseStateHandle<T>: Manages local component state. Returns a handle that can be dereferenced to get the current value and has a .set(new_value) method to update the state. Updating state triggers a re-render.
    use yew::prelude::*;
    
    #[function_component]
    fn ClickCounter() -> Html {
        let count = use_state(|| 0); // Initializes with 0
        let onclick = {
            let count = count.clone(); // Clone the handle for the closure
            move |_| {
                count.set(*count + 1); // Update the state
            }
        };
        html! { <button {onclick}>{"Clicked "}{*count}{" times"}</button> }
    }
    
  • use_state_eq<T: PartialEq>() -> UseStateHandle<T>: Similar to use_state, but only triggers a re-render if the new value is not equal to the current value (using PartialEq).
  • use_memo<T, D>(f: impl FnOnce(D) -> T, deps: D) -> Rc<T>: Memoizes an expensive computation. The f closure is only re-executed if deps (dependencies) change.
  • use_callback<IN, OUT, F>(f: F) -> Callback<IN, OUT>: Memoizes a Callback instance. The Callback is only recreated if its dependencies (captured variables) change.
  • use_mut_ref<T>() -> Rc<RefCell<T>>: Provides a mutable reference (RefCell) that persists across re-renders without triggering them. Useful for mutable data that doesn't directly affect rendering or for interop with mutable JS APIs.
  • use_node_ref() -> NodeRef: Creates a NodeRef handle, used to get a direct reference to a rendered DOM element.
    use yew::prelude::*;
    use web_sys::HtmlInputElement;
    
    #[function_component]
    fn MyInput() -> Html {
        let input_ref = use_node_ref();
        let current_value = use_state(|| String::new());
    
        let on_input_change = {
            let input_ref = input_ref.clone();
            let current_value = current_value.clone();
            move |_| {
                if let Some(input) = input_ref.cast::<HtmlInputElement>() {
                    current_value.set(input.value());
                }
            }
        };
    
        html! {
            <div>
                <input ref={input_ref} type="text" oninput={on_input_change} />
                <p>{"Input Value: "}{&*current_value}</p>
            </div>
        }
    }
    
  • use_reducer<R>() -> UseReducerHandle<R>: Manages more complex state using a reducer pattern (similar to Redux), allowing state updates based on dispatched "actions". The state must implement the Reducible trait.
  • use_reducer_eq<R: PartialEq>() -> UseReducerHandle<R>: Similar to use_reducer, but dispatches only if the new state differs from the old.
  • use_effect(f: impl FnOnce() -> impl FnOnce()): Runs a side effect after every render. Can return a cleanup closure that runs before the next effect or when the component is unmounted.
  • use_effect_with<D>(deps: D, f: impl FnOnce(D) -> impl FnOnce()): Runs a side effect only when deps (dependencies) change.
  • use_context<T: Clone + PartialEq>() -> Option<Rc<T>>: Accesses a context value provided by an ancestor ContextProvider component.
  • use_force_update(): Returns a callback that, when emitted, forces a re-render of the component. Use sparingly.

Custom Hooks: Components can extract reusable stateful logic into custom hooks by defining functions starting with use_ and marking them with #[hook]. Custom hooks compose existing hooks.

Callbacks

Callback is a crucial type for event handling and child-to-parent communication. It wraps an Fn closure in an Rc, making it cheaply clonable.

  • Callback::from(closure): Creates a Callback from a closure.
  • callback.emit(value): Invokes the wrapped closure with the given value.
  • DOM Events: Event handlers in html! (e.g., onclick, oninput) expect a Callback that takes the corresponding web_sys event type as an argument.
    use yew::prelude::*;
    use web_sys::MouseEvent;
    
    #[function_component]
    fn MyButton() -> Html {
        let onclick_handler = Callback::from(move |e: MouseEvent| {
            // Access event properties:
            log::info!("Click event at: ({}, {})", e.client_x(), e.client_y());
            // More complex logic...
        });
        html! { <button onclick={onclick_handler}>{"Click Me"}</button> }
    }
    
  • TargetCast Trait: Provided by Yew (within yew::prelude::*), this trait extends web_sys::Event to safely cast the event target to a specific HTML element type (e.g., HtmlInputElement).
    • event.target_dyn_into::<HtmlElementType>() -> Option<HtmlElementType> (safe, checked)
    • event.target_unchecked_into::<HtmlElementType>() -> HtmlElementType (unchecked, use with caution when type is guaranteed)

Contexts

Contexts provide a way to pass data deeply through the component tree without manually "prop drilling" at every level.

  • Provider (ContextProvider): An ancestor component wraps its children with ContextProvider<T>, providing a value of type T. T must implement Clone and PartialEq.
    // Define your context data
    #[derive(Clone, Debug, PartialEq)]
    struct Theme {
        foreground: String,
        background: String,
    }
    
    #[function_component]
    fn ThemeProvider(props: &ChildrenProps) -> Html { // ChildrenProps from yew::html
        let theme = use_state(|| Theme {
            foreground: "#000".to_string(),
            background: "#eee".to_string(),
        });
        html! {
            <yew::ContextProvider<Theme> context={(*theme).clone()}>
                { props.children.clone() }
            </yew::ContextProvider<Theme>>
        }
    }
    // Usage: html! { <ThemeProvider> <ArbitraryDeepComponent /> </ThemeProvider> }
    
  • Consumer (use_context hook): Descendant function components use use_context::<T>() to retrieve the provided value.
    #[function_component]
    fn ThemedText() -> Html {
        let theme = use_context::<Theme>() // Retrieve the Theme context
            .expect("Theme context not provided!");
        html! {
            <p style={format!("color: {}; background: {};", theme.foreground, theme.background)}>
                {"This text is themed."}
            </p>
        }
    }
    
  • Mutable Contexts: To allow children to modify a context value, combine ContextProvider with use_reducer for a predictable state update mechanism.

Event Handling and Delegation

Yew integrates with web-sys for DOM events. Yew's event system employs event delegation: listeners are not directly attached to individual elements but are handled by a single delegate at the application's root. Events then "bubble up" through Yew's Virtual DOM hierarchy.

  • Event Listener Names: In html!, event listeners start with on followed by the event name (e.g., onclick, oninput).
  • Manual Event Listeners: For events not directly supported by html! or for fine-grained control, use use_effect_with and gloo-events (EventListener) to manually attach/detach event listeners to NodeRef-obtained DOM elements.
    use gloo::events::EventListener;
    use yew::prelude::*;
    use web_sys::HtmlElement;
    
    #[function_component]
    fn CustomEventHandler() -> Html {
        let div_ref = use_node_ref();
        use_effect_with(div_ref.clone(), {
            let div_ref_clone = div_ref.clone();
            move |_| {
                let mut listener_obj: Option<EventListener> = None;
                if let Some(div_element) = div_ref_clone.cast::<HtmlElement>() {
                    let listener = EventListener::new(&div_element, "custom-event", move |event| {
                        log::info!("Custom event received!");
                        // event.dyn_into::<web_sys::CustomEvent>() for richer data
                    });
                    listener_obj = Some(listener);
                }
                move || drop(listener_obj) // Cleanup when effect re-runs or component unmounts
            }
        });
        html! { <div ref={div_ref}>{"Div with custom event listener"}</div> }
    }
    

Advanced Topics

Interacting with JavaScript (JS) and Web APIs

Yew compiles to Wasm, but direct interaction with browser APIs often involves wasm-bindgen and related crates.

  • wasm-bindgen: Bridges calls between Rust and JavaScript. Used for defining FFI (Foreign Function Interface) to import/export functions.
  • web-sys: Provides Rust bindings for all Web APIs (DOM, Fetch, etc.). This is the primary way to interact with the browser from Rust.
    • Features: web-sys is heavily feature-gated; enable only the necessary features in Cargo.toml to avoid bloat (e.g., features = ["Document", "HtmlElement", "Window"]).
    • Inheritance: web-sys types simulate JavaScript inheritance using Rust's Deref and AsRef traits. For instance, an HtmlElement can deref to Element, then Node, then EventTarget, finally JsValue. This allows calling methods from ancestor types.
    • JsCast Trait: Crucial for downcasting JsValue or generic EventTarget (from web_sys) to specific, more concrete types (e.g., HtmlInputElement). Provides dyn_into (checked, returns Result) and unchecked_into (unchecked, faster).
  • js-sys: Provides Rust bindings for JavaScript's standard, built-in objects (e.g., Date, Object).
  • wasm-bindgen-futures: Bridges Rust Futures with JavaScript Promises.
    • spawn_local(future): Spawns a Rust Future to run on the current thread's event loop, enabling asynchronous operations (e.g., async fetch() calls).

Asynchronous Operations and Suspense

Yew's Suspense component allows suspending component rendering while waiting for an asynchronous task (e.g., data fetching) to complete, showing a fallback UI in the interim. This enables a "Render-as-You-Fetch" pattern.

  • Suspense Component: Wraps children components that might "suspend". Requires a fallback prop, which is the Html to render during suspension.
  • Suspending Hooks: A hook can signal suspension by returning Err(Suspension).
  • Suspension::new(): Creates a Suspension and a SuspensionHandle. When handle.resume() is called (or handle is dropped), the suspended component re-renders.
    use yew::prelude::*;
    use yew::suspense::{Suspension, SuspensionResult};
    
    // A hypothetical async data loading function
    async fn fetch_user_data() -> String { "AI Robot Coder".to_string() }
    
    // This hook will suspend rendering until user data is fetched
    #[hook]
    fn use_current_user() -> SuspensionResult<String> {
        let (suspension, handle) = Suspension::new(); // Create suspension
        let user_data_state = use_state(|| None ); // Local state for fetched data
    
        // If data is already there, return Ok.
        if let Some(data) = &*user_data_state {
            return Ok(data.clone());
        }
    
        // If not, spawn an async task and signal suspension.
        let user_data_state_clone = user_data_state.clone();
        wasm_bindgen_futures::spawn_local(async move {
            let data = fetch_user_data().await;
            user_data_state_clone.set(Some(data)); // Update state
            handle.resume(); // Signal the suspension to resume
        });
    
        Err(suspension) // Signal that the component should suspend
    }
    
    #[function_component]
    fn UserDisplay() -> HtmlResult { // HtmlResult instead of Html for suspending components
        let user_name = use_current_user()?; // The '?' operator propagates the suspension
        Ok(html! { <p>{ format!("Hello, {}!", user_name) }</p> })
    }
    
    #[function_component]
    fn App() -> Html {
        html! {
            <Suspense fallback={html!{<p>{"Loading user..."}</p>}}>
                <UserDisplay />
            </Suspense>
        }
    }
    

Routing (yew-router crate)

yew-router provides client-side routing for Single Page Applications (SPAs).

  • Routable Enum: Define routes as an enum deriving Routable. Each variant maps to a URL path using #[at("/path")]. #[not_found] designates the fallback route.
    • Path Segments: Define dynamic segments with :segment_name (for single segments) or *wildcard (for multi-segment wildcards). These become fields in the enum variant.
  • BrowserRouter: The main router component that manages browser history. All <Switch /> and <Link /> components must be descendants of BrowserRouter.
  • Switch Component: Takes an Routable enum and a render function. The render function receives the matched route variant and returns the Html to display.
  • Link Component: A component that renders an <a> tag but performs client-side navigation (pushState) instead of a full page reload.
  • Navigation API (use_navigator()): Provides programmatic navigation via navigator.push(&Route) or navigator.replace(&Route).
  • Query Parameters: Can be included in navigation by passing a serializable struct/map to push_with_query() or retrieved from location.query().
  • Nested Routers: Allows for modular routing within sub-sections of the application, often using a * wildcard in the parent router to delegate to a child router.
  • Basename: Configure a common prefix for all routes, useful when the application is served from a sub-path.

Web Workers and Agents

Yew Agents are a mechanism for offloading tasks to Web Workers, enabling concurrent processing and preventing UI unresponsiveness.

  • Types of Agents:
    • Public: A single instance shared across all bridges in a web worker.
    • Private: A new instance spawned for each bridge connection.
  • Communication:
    • Bridges: Bi-directional communication between a component and an agent, or between agents.
    • Dispatchers: Uni-directional communication from a component to an agent.
  • Overhead: Agents introduce serialization/deserialization overhead for messages between threads (using bincode), making them suitable for chunky computations rather than frequent, small messages.

Deployment Considerations

  • Release Build: Use trunk build --release for optimized, production-ready builds.
  • Server Configuration:
    • SPA Fallback: Configure the HTTP server to serve index.html as a fallback for any unmatched URL paths, allowing the yew-router to handle client-side routing.
    • MIME-type: Ensure .wasm files are served with the application/wasm MIME-type.
  • Relative Paths: Use <base data-trunk-public-url /> in index.html and trunk build --public-url /your/path/ to deploy the app under a sub-path.
  • Environment Variables: Use std::env!("VAR_NAME") to embed environment variables at compile time (runtime access in browser is not direct).

Debugging and Testing

Debugging

  • Panics: Yew automatically logs Rust panics to the browser's developer console.
  • Console Logging:
    • wasm-logger: Integrates Rust's log crate with the browser console.
    • gloo-console: Provides log! macro for direct JsValue logging to console.
    • tracing-web: Integrates tracing framework with browser console and performance API.
  • Component Lifecycles: Use tracing to gain insights into component re-renders and hook execution.
  • Source Maps: Limited support available; requires specific configuration.

Testing

  • wasm_bindgen_test: Enables running Rust tests directly in a browser environment.
  • Snapshot Testing: Yew provides utilities (yew::tests::layout_tests) for snapshot testing component output.
  • Shallow Rendering: Work in progress for testing components in isolation without rendering their full subtree.