# State

State lives in entities. A view reads it in `render`; a handler changes it and calls `cx.notify()`.

> **Example: Hello.** Source in [examples/hello](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/hello). Live on the web and captured on macOS, Linux and Windows in the docs app.


## Models and observation

A model is an entity with no `render`. A view that shows it observes it, and the observer runs after
the model calls `cx.notify()`:

```rust
struct Tally {
    total: u32,
}

struct Readout {
    shown: u32,
}

impl Readout {
    fn new(tally: &Entity<Tally>, cx: &mut Context<Self>) -> Self {
        cx.observe(tally, |this, tally, cx| {
            this.shown = tally.read(cx).total;
            cx.notify();
        })
        .detach();
        Self { shown: tally.read(cx).total }
    }
}

impl Render for Readout {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        text(format!("total {}", self.shown))
    }
}
```

<sub>From [examples/hello/tests/state.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/hello/tests/state.rs), line 7.</sub>


`cx.observe` returns a `Subscription`. Dropping it ends the observation, so a constructor that wants
the observer for the view's whole life calls `.detach()`. The observer gets `&mut Self`, the observed
entity and a `Context<Self>`; it copies what it needs and notifies itself. The test changes the model
from outside the view and reads the result off the test backend:

```rust
let tally = cx.new(|_| Tally { total: 0 });
let model = tally.clone();
let window = cx.open_window(Size { width: 400., height: 200. }, move |_, cx| cx.new(|cx| Readout::new(&model, cx)));
assert!(window_text(cx, window.id()).contains("total 0"));

tally.update(cx, |tally, cx| {
    tally.total += 3;
    cx.notify();
});
assert!(window_text(cx, window.id()).contains("total 3"));
```

<sub>From [examples/hello/tests/state.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/hello/tests/state.rs), line 36.</sub>


Observation is how a child tells its parent that something changed: the shared model is the channel.
A view does not emit typed events to its parent yet; there is no `EventEmitter` or `cx.subscribe` in
`nd-ui`. Native controls have events (`on_click`, `on_change`, `on_submit`), and those reach your
view through `cx.listener`.

## Controlled values

Controls that hold a value (`slider`, `select`, `text_field`, tabs) are controlled. The app owns the
value, passes it in on every render and writes the new one back in the change handler:

```rust
form().child(
    form_section()
        .title("Reading")
        .child(form_row("Density").child(
            select("density", ["Comfortable", "Compact"]).selected(self.density).on_change(cx.listener(
                |this, e: &SelectChanged, _, cx| {
                    this.density = e.index;
                    cx.notify();
                },
            )),
        ))
        .child(form_row("Text size").help("Applies to the message list").child(
            slider("size", self.size).range(11.0..=24.0).w(px(180.)).on_change(cx.listener(
                |this, e: &SliderChanged, _, cx| {
                    this.size = e.value;
                    cx.notify();
                },
            )),
        )),
)
```

<sub>From [examples/chrome/src/lib.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/chrome/src/lib.rs), line 133.</sub>


The framework keeps a record of what each native control holds and applies a value only when it
differs. Setting the value the control already shows sends nothing, and events caused by the
framework's own setter calls are dropped, so feeding a value back never produces a second set or an
event loop. If a handler refuses a change by leaving the state alone, the control is set back to the
app's value on the next frame. When the control normalises a value (a slider snapping to its step),
the app receives the normalised value once.

## Background work

There is no `cx.spawn` or executor timer in `nd-ui` yet. Off-thread work goes through
`nd::system::spawn(future, done)`: the future runs off the main thread, and `done` runs on the main
thread with its output and the `App`. `done` may hold entities, so it holds a weak one and updates
the view through it:

```rust
/// Runs `call` and logs its outcome under `what`.
fn run<T: std::fmt::Debug + Send + 'static>(
    &mut self,
    cx: &mut Context<Self>,
    what: &'static str,
    call: impl std::future::Future<Output = nd::system::Result<T>> + Send + 'static,
) {
    let view = cx.weak_entity();
    nd::system::spawn(call, move |result, app| {
        view.update(app, |this, cx| {
            match result {
                Ok(value) => this.note(format!("{what}: {value:?}")),
                Err(e) => this.note(format!("{what} failed: {e}")),
            }
            cx.notify();
        });
    });
}
```

<sub>From [examples/system/src/lib.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/system/src/lib.rs), line 123.</sub>


`WeakEntity::update` returns `None` when the view has closed, and the result is ignored. The same
shape serves every callback that outlives the handler that created it, such as the answer of an alert
or a file dialog:

```rust
fn delete(&mut self, _: &DeleteMessage, window: &mut Window, cx: &mut Context<Self>) {
    let this = cx.weak_entity();
    let alert =
        Alert::new("Delete this message?").message("It moves to the Trash.").destructive("Delete").cancel("Cancel");
    window.alert(cx, alert, move |answer, cx| {
        let _ = this.update(cx, |this, cx| {
            this.status = if answer.0 == 0 { "Deleted".into() } else { "Kept".into() };
            cx.notify();
        });
    });
}
```

<sub>From [examples/chrome/src/lib.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/chrome/src/lib.rs), line 20.</sub>


Call `spawn` from the main thread. In a tier 1 test no host runs the continuations, so call
`nd::system::run_pending(cx)` after the future finishes; the [system example's test](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/system/tests/tier1.rs) does.

## Clocks and timers

Use `nd::time` for `Instant` and `SystemTime`. On `wasm32` the standard versions compile and then
panic when called, and `nd::time` swaps in browser-safe ones. `nd check` flags `std::time::Instant`,
`std::time::SystemTime` and `std::thread::sleep` in UI code.

## Errors

Return `Result` from your own code and show failures in the UI: a `Err` from a system call is a
value in `done`, as the `run` helper above logs it. A panic is not a UI state. On a panic, a native
crash or a hang the diagnostics layer writes an incident bundle (UI tree, recent inputs, frame
timings, logs) under the app data directory, and `nd diagnose` summarises the bundles. Call
`nd::report_issue("what happened")` to write one on demand; it returns the bundle directory, or
`None` on the web, where only the panic hook to the browser console exists.

## Saved state

`nd_settings::Store<T>` keeps machine state such as window frames, recents or a library: JSON on disk
as `{"version": N, "data": ..}`, written after a short debounce through a temp file and rename, and
flushed on drop. `Store::open(path, version, migrate)` loads it. A missing file gives
`T::default()`. A file that cannot be read or migrated is moved aside as
`<name>.corrupt-<unix time>`, defaults win, and `load_error()` says why. `get()` returns an
`Arc<T>` and `update(|data| ..)` changes it and schedules the write. On the web the same type writes
to `localStorage`.

The store is not re-exported through `nd` and no example in the repository uses it yet, so there is
no snippet here. Add `nd-settings` to your `Cargo.toml` to use it. User-facing options belong in the
settings layer instead; see [settings and config](https://nativedesktop.formalsnake.dev/guides/settings-and-config.md). The
[source](https://github.com/FormalSnake/NativeDesktop/blob/main/crates/nd-settings/src/store.rs) is short and documents each call.
