# Chapter 15: Reactive UI

> ⚠ **Planned — not in Loup 1.25.** This chapter describes the design in the Language Reference (§2-4 Reactive UI: §2-4.1 signals, §2-4.2 components, §2-4.3 mounting, §2-4.6 the `ui` fence, §2-4.8 the desktop backend). The toolchain does not implement it yet, so its listings are design sketches and are not verified.

**What you'll learn:** the planned design of Loup's reactive UI — signals, derived values and effects, components written as functions that return a `View`, the cross-platform `ui` fence, and mounting a view in a window.

## Status in Loup 1.25

None of the `ui` modules ship. `from ui import (signal);` fails with `'ui' is not a standard-library module`, and `signal`, `derived`, `effect`, `View`, `ComponentRegistry` and `mount` are undefined. A `(ui)` template fence does compile, but only as an ordinary text template: rendering it returns its markup as a string, not a `View`. No desktop or browser renderer exists.

What you can do today is generate HTML text with `html` template fences (Chapter 12) and serve it with the `net.http` server module; Chapter 18 builds command-line tools, which is where most scripts end up.

## The Design in Brief

The UI system has three layers (§2-4): a universal core, `ui`, with signals and the component model; a web backend, `ui.web`, for browsers and server-side rendering; and a desktop backend, `ui.desktop`, for native windows. A `host.scripted` file uses the core and the desktop backend; `ui.web` is for the `browser` and `service` targets and is not available to scripts (§1-2.4.3).

## Signals, Derived Values and Effects

A *signal* is a value that records who reads it and notifies them when it changes (§2-4.1). `derived(fn)` is a read-only signal computed from others; `effect(fn)` runs `fn` once immediately and again whenever a signal it read changes:

```loup,ignore
@mode(host.scripted)

from ui import (signal, derived, effect);

let celsius = signal(20.0);
let fahrenheit = derived(func() => celsius.get() * 9.0 / 5.0 + 32.0);

let watcher = effect(func() {
    println(i"{$ celsius.get() $} C = {$ fahrenheit.get() $} F");
});
# prints "20 C = 68 F" immediately

celsius.set(30.0);
# prints "30 C = 86 F"

celsius.update(func(c: float) => c + 5.0);
# prints "35 C = 95 F"

watcher.dispose();
celsius.set(0.0);
# prints nothing: the effect was disposed
```

*Listing 15-1: A signal, a derived signal and an effect (design sketch)*

The API is small: `Signal[T]` has `get()`, `set(value)` and `update(fn)`; `effect` returns an `EffectHandle` with `dispose()`.

## Components and the `ui` Fence

A component is a function that returns a `View` (§2-4.2). Its body runs once, when the component is created, so local signals hold its state. The markup is written in a `ui` template fence whose tags — `Column`, `Row`, `Text`, `Button`, `TextField`, `Checkbox` and the rest of §2-4.6 — map to native widgets or to HTML, depending on the backend. Uppercase tags are looked up in a `ComponentRegistry`, named in the fence's `components=` descriptor:

````loup,ignore
@mode(host.scripted)

from ui import (signal, View, ComponentRegistry);
import ui.primitives;

let registry = ComponentRegistry.of(ui.primitives.components);

func Counter(start: int): View {
    let count = signal(start);
    return ```(ui):(components=registry)
        <Column spacing={$ 8 $}>
            <Text>Count: {$ count.get() $}</Text>
            <Row spacing={$ 8 $}>
                <Button onclick={$ func() { count.update(func(n: int) => n + 1); } $}>
                    <Text>+1</Text>
                </Button>
                <Button onclick={$ func() { count.set(start); } $}>
                    <Text>Reset</Text>
                </Button>
            </Row>
        </Column>
    ```;
}
````

*Listing 15-2: A counter component (design sketch)*

Reading `count.get()` inside the fence makes the text depend on `count`; clicking a button changes the signal and the runtime re-renders only what depends on it. Event handlers are Loup closures, never strings of JavaScript (`E_INLINE_JS_EVENT_HANDLER`). The fence directives of Chapter 12 — `{% if %}`, `{% for %}`, `{% match %}` — work inside `ui` fences, and `{% suspend fallback={$ … $} %}` shows a placeholder while an `async` component's `Task[View]` resolves (§2-4.5).

Input widgets bind to a signal directly: `TextField(value: Signal[string])`, `Checkbox(checked: Signal[bool])`.

## Mounting

`mount(view, target)` attaches a view to a rendering root and returns a handle with `unmount()` (§2-4.3). `runtime.ui.rootMount()` gives the platform's default root, so one entry point works on every backend. A script would open a desktop window:

````loup,ignore
@mode(host.scripted)

from ui import (signal, mount, View, ComponentRegistry);
import ui.primitives;

let registry = ComponentRegistry.of(ui.primitives.components);

func TodoApp(): View {
    let items = signal(list[string]());
    let draft = signal("");

    return ```(ui):(components=registry)
        <Column spacing={$ 12 $}>
            <Row spacing={$ 8 $}>
                <TextField value={$ draft $} placeholder="Add item..." />
                <Button onclick={$ func() {
                    items.update(func(xs: list[string]) => xs + [draft.get()]);
                    draft.set("");
                } $}>
                    <Text>Add</Text>
                </Button>
            </Row>
            {% for (item in items.get()) %}
                <Text>{$ item $}</Text>
            {% end %}
        </Column>
    ```;
}

mount(TodoApp(), runtime.ui.rootMount());
````

*Listing 15-3: A to-do list mounted at the platform's default root (design sketch)*

The list is updated by producing a new list (`xs + [draft.get()]`) rather than mutating the old one; treating the values held in signals as immutable is the intended style.

Custom elements (`@webcomponent`), server-side rendering and hydration belong to the web backend; see [The Loup Web Book](../loup-web/README.md), which describes them as planned as well.

## Summary

- Reactive UI is specified in §2-4 and not implemented in Loup 1.25: the `ui` modules do not exist, and a `(ui)` fence renders only as text.
- The design: `signal`, `derived` and `effect` for state; components are functions returning `View`, written with the `ui` fence and a `ComponentRegistry`; `mount(view, target)` attaches a view, with `runtime.ui.rootMount()` as the portable target.
- Scripts are meant to use the core `ui` module and the desktop backend; `ui.web` is for browser and service targets.
- Until it ships, generate HTML with `html` fences (Chapter 12).
