Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

.nornir/elm-for-idiots.md

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 instead — this one spells out every keystroke.

What you need first

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:

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:

#[derive(Clone, Debug)]
enum MyMsg { Pick(u64), Clear /* …one per change… */ }

Step 5 — implement Elm

Add this block (fill the match):

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:

#[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.