# The Elm architecture — guide
> **Class:** `guide` · audience: a techie writing or driving a facett component.
> The *what* is in [`elm-design.md`](elm-design.md); the *why* in [`elm-reasoning.md`](elm-reasoning.md).
> This file is the workflow.
## f0 — zero to a tested component (the whole workflow)
Four moves turn a struct into a harness-testable facett component:
1. **Name the state.** One struct = your `Model`: `#[derive(Serialize, Deserialize, Clone, PartialEq)]`.
Everything you'd assert on goes here; caches/animation do **not** (put those in
`Cell`/`RefCell` fields on the view, not in `Model`).
2. **Name the inputs.** One `enum Msg: Clone + Debug`. Every click, key, drag = a variant.
3. **`impl Elm`.** `update(&mut self, Msg)` is your only mutation; `view(&self, ui)` paints and
returns the `Msg`s the frame produced. No I/O in `update` — return `Effect` data (or use
`enum Effect {}` if there is none).
4. **One macro line.** `facett_core::impl_facet_via_elm!(MyView);` — now it's a `Facet`.
Then it is *automatically* testable headless (see `f3`). That's the payoff: no window in the test.
## f1 — filling the trait
```rust
struct MyView { state: MyModel, /* + non-Model caches: */ layout: RefCell<Option<Layout>> }
impl facett_core::Elm for MyView {
type Model = MyModel;
type Msg = MyMsg;
type Effect = (); // or a real enum; or `enum NoFx {}` for "pure"
fn title(&self) -> &str { "my view" }
fn state(&self) -> &MyModel { &self.state }
fn update(&mut self, m: MyMsg) -> Vec<()> {
match m { MyMsg::Pick(id) => self.state.selected = Some(id), /* … */ }
vec![]
}
fn view(&self, ui: &mut egui::Ui) -> Vec<MyMsg> {
let mut out = vec![];
if ui.button("pick").clicked() { out.push(MyMsg::Pick(some_id)); }
out // never mutate self here
}
}
```
Rule of thumb: if you're about to write `&mut self` anywhere but `update`, stop — that state
change is a `Msg`.
## f2 — choosing the macro form
| you need… | use |
|---|---|
| just a `Facet` from your `Elm` | `impl_facet_via_elm!(MyView)` |
| that **plus** extra `Facet` methods (but keep default `state_json`) | `impl_facet_via_elm!(MyView, { fn foo(&self){…} })` |
| a **richer `state_json`** than `serde(state())` | `impl_facet_via_elm!(MyView, custom_state_json, { fn state_json(&self)->Value {…} })` |
Most components use form 1 or the `custom_state_json` form (a fuller introspection json).
## f3 — driving it in a test (the whole point)
```rust
use facett_core::harness;
let mut v = MyView::new();
// Apply a Msg script and inspect the resulting Model — NO window, NO GPU:
let model = harness::snapshot(&mut v, [MyMsg::Pick(id_a), MyMsg::Pick(id_b)]);
assert_eq!(model.selected, Some(id_b));
// Golden-file style:
let json = harness::snapshot_json(&mut v, [MyMsg::Pick(id_a)]);
// Assert it actually paints (offscreen), for the render-report path:
assert!(harness::headless_render(&mut v).drew());
```
`drive` returns the `Effect`s so you can assert "this Msg requested effect X" without X
happening. This is the same seam nornir's **test-matrix** and **robot** functions consume at
scale — see [nornir test-function design](../../nornir/.nornir/test-design.md).
## Look & feel
A component inherits facett's look automatically: paint through the `egui::Ui` you're given
and use `facett_core::look`/`Theme` (the harness has `render_themed` to snapshot a component
under a specific `Theme`). Don't hard-code colours; pull from the theme so every component
reads as one product.