# State

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

> **Example: Hello.** Source in [examples/hello](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/hello). Its page on the docs site shows it captured on macOS, Linux and Windows.


## 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/FormalStudio/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/FormalStudio/NativeDesktop/blob/main/examples/hello/tests/state.rs), line 36.</sub>


A child tells its parent that something changed through observation, with the shared model as the
channel. A view can't emit typed events to its parent yet, because `nd-ui` has no `EventEmitter` or
`cx.subscribe`. 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. Your 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/FormalStudio/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 that the control already shows sends nothing, and the framework drops
events that its own setter calls cause, 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 framework sets the control back
to the app's value on the next frame. When the control normalizes a value (a slider snapping to its
step), the app receives the normalized value once.

## Background work

`nd-ui` has no `cx.spawn` or executor timer yet. Run off-thread work through
`nd::system::spawn(future, done)`. The future runs off the main thread, and `done` runs on the main
thread with the future's output and the `App`. When `done` needs a view, give it a weak entity and
update the view through that:

```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/FormalStudio/NativeDesktop/blob/main/examples/system/src/lib.rs), line 123.</sub>


`WeakEntity::update` returns `None` when the view has closed, and the code ignores the result. Use
the same shape for every callback that outlives the handler that created it, such as the answer from
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/FormalStudio/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, as the
[system example's test](https://github.com/FormalStudio/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. An `Err` from a system call is a
value in `done`, and the `run` helper above logs it. A panic isn't 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` summarizes the bundles. To write one
on demand, call `nd::report_issue("what happened")`. It returns the bundle directory, or `None` on
the web, where diagnostics install only the panic hook, which writes to the browser console.

## Saved state

`nd_settings::Store<T>` keeps machine state such as window frames, recents, or a library. It stores
JSON on disk as `{"version": N, "data": ..}`, writes it through a temp file and rename after a short
debounce, and flushes it on drop. `Store::open(path, version, migrate)` loads it. A missing file
gives `T::default()`. The store moves a file that it can't read or migrate aside as
`<name>.corrupt-<unix time>`, uses the defaults, and reports why in `load_error()`. `get()` returns
an `Arc<T>`, and `update(|data| ..)` changes it and schedules the write. On the web, the same type
writes to `localStorage`.

`nd` doesn't re-export the store, and no example in the repository uses it yet, so this page has no
snippet. To use it, add `nd-settings` to your `Cargo.toml`. User-facing options belong in the
settings layer instead. See [settings and config](https://native.formalsnake.dev/guides/settings-and-config.md). The
[source](https://github.com/FormalStudio/NativeDesktop/blob/main/crates/nd-settings/src/store.rs) is short and documents each call.
