# The Elm architecture — for idiots
> **Class:** `idiot`. Every step, nothing assumed. Goal: **migrate one existing facett
> component to the Elm shape**, from a cold shell to a green test. If you already know Rust,
> read [`elm-guide.md`](elm-guide.md) instead — this one spells out every keystroke.
## What you need first
- A terminal (the black window where you type commands).
- The facett repo on disk. In the terminal, type this and press **Enter**:
```
cd /run/media/rickard/T9/git/facett
```
## Step 1 — pick a component and open it
1. In the terminal, type and press **Enter**:
```
ls facett-*/src/lib.rs
```
Each line is a component. Pick one that is **not** yet Elm — type and press **Enter**:
```
grep -L "impl_facet_via_elm\|impl.*Elm for" facett-*/src/lib.rs
```
Any file it prints is a candidate. Open one in your editor.
## Step 2 — find the state and the mutations
Read the file. Two questions:
- **"What does this thing remember?"** — the fields it reads when drawing. That is your **Model**.
- **"What changes those fields?"** — every place it writes `self.something = …`. Each is a **Msg**.
Write them down. You are going to move every write into one function.
## Step 3 — make the Model serializable
Above the state struct, make sure this line is there (add what's missing):
```
#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq)]
```
If a field can't be serialized (a GPU handle, an animation clock), it does **not** belong in
the Model — move it to a separate field on the *view* wrapped in `std::cell::RefCell`.
## Step 4 — write the Msg enum
Just under the struct, add one enum listing every change from Step 2:
```rust
#[derive(Clone, Debug)]
enum MyMsg { Pick(u64), Clear /* …one per change… */ }
```
## Step 5 — implement Elm
Add this block (fill the `match`):
```rust
impl facett_core::Elm for MyView {
type Model = MyModel;
type Msg = MyMsg;
type Effect = (); // leave as () unless it starts threads / writes files
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),
MyMsg::Clear => self.state.selected = None,
}
vec![]
}
fn view(&self, ui: &mut egui::Ui) -> Vec<MyMsg> {
let mut out = Vec::new();
// move your OLD drawing code here, but instead of `self.x = y` write `out.push(MyMsg::…)`
out
}
}
```
Two hard rules: **`view` never writes to `self`** (if the compiler complains it needs `&mut`,
you left a mutation in `view` — turn it into a pushed `Msg`). And **all your old writes now
live only inside `update`.**
## Step 6 — the one macro line
Right below the `impl Elm`, add:
```
facett_core::impl_facet_via_elm!(MyView);
```
Delete the old hand-written `impl Facet for MyView` if there was one (the macro writes it now).
## Step 7 — build it
Type and press **Enter**:
```
cargo build -p facett-<yourcomponent>
```
Red text = errors; fix what it names (usually a leftover `self.x =` inside `view`) and run it
again until it's quiet.
## Step 8 — write the headless test
At the bottom of the file add:
```rust
#[test]
fn pick_then_clear() {
use facett_core::harness;
let mut v = MyView::new();
let model = harness::snapshot(&mut v, [MyMsg::Pick(7), MyMsg::Clear]);
assert_eq!(model.selected, None);
}
```
Run it — type and press **Enter**:
```
cargo test -p facett-<yourcomponent>
```
Green `test result: ok` = done. You just tested a GUI component **without opening a window**.
## Step 9 — save your work
Type these three, pressing **Enter** after each:
```
git add -A
git commit -m "facett-<component>: migrate to Elm"
git push
```
That's the whole migration. One component, one commit. Do the next one the same way.