# Quick start

> A small notes app with a route table, one screen type, one scope and a frame loop, drawn with egui.

This guide builds a cut-down version of the `notes` example that ships with Carte. It has three screens, a scope that exists only while the editor is open, and a frame loop. egui draws it, but nothing on the Carte side knows that. It assumes you have finished [Installation](/docs/installation) and added `eframe = "0.31"` to your dependencies.

## 1. Describe the table

```rust
use carte::schema::{Field, Kind, Lit, Schema};
use carte::{
    set, Departure, Escape, Node, Occlusion, Outbox, Producer, RouteId, RouteTable, Router, Scope,
    Store,
};
use eframe::egui;

fn table() -> RouteTable {
    let mut table = RouteTable::new();
    table
        .at_root("home")
        .under("note", "home")
        .under("settings", "home")
        .under("settings", "note"); // one screen, two parents
    table
}
```

The table says what may appear beneath what. It does not say what is showing. `settings` has two parents, so Back returns to whichever one you came from with no extra bookkeeping.

## 2. Write the model and its actions

The model is your app. Carte reads it to derive the path, but never writes it. The UI asks for changes with actions.

```rust
struct Model {
    notes: Vec<String>,
    open: Option<usize>,
    /// The child each screen is showing, if any.
    home_child: Option<RouteId>,
    note_child: Option<RouteId>,
}

#[derive(Debug)]
enum Action {
    Open(usize),
    Edit(String),
    OpenSettings { from: RouteId },
    /// `parent` stops showing its child: what a pop turns into.
    Closed { parent: RouteId },
}

impl Model {
    fn apply(&mut self, action: &Action) {
        match action {
            Action::Open(i) => {
                self.open = Some(*i);
                self.home_child = Some("note");
            }
            Action::Edit(text) => {
                if let Some(i) = self.open {
                    self.notes[i] = text.clone();
                }
            }
            Action::OpenSettings { from: "note" } => self.note_child = Some("settings"),
            Action::OpenSettings { .. } => self.home_child = Some("settings"),
            Action::Closed { parent: "home" } => {
                self.home_child = None;
                self.note_child = None;
                self.open = None;
            }
            Action::Closed { parent: "note" } => self.note_child = None,
            Action::Closed { .. } => {}
        }
    }
}
```

Opening a screen is setting `home_child`. There is no `push`.

## 3. Declare a scope

The editor's state lives in a scope keyed on the `note` node. It mounts when `note` joins the path and is dropped when it leaves.

```rust
#[derive(Clone, Debug, PartialEq)]
struct Editor {
    body: String,
    words: usize,
}

impl Schema for Editor {
    fn reflect() -> Kind {
        Kind::Record(vec![
            Field::new("body", Kind::Str),
            Field::new("words", Kind::Int),
        ])
    }
    fn at_mount(_: Option<&Lit>) -> Self {
        Self {
            body: String::at_mount(None),
            words: usize::at_mount(None),
        }
    }
}

const EDITOR: Scope = Scope::Node("note");
```

The `Schema` impl is written by hand here. A derive will write it for you once it lands in the crate. See [Schemas](/docs/state/schemas).

## 4. Implement a node

Every id in the table needs a handler that implements `Node`. One small type covers all three screens here.

```rust
struct Screen {
    id: RouteId,
    child: fn(&Model) -> Option<RouteId>,
}

impl Node<Model, Action> for Screen {
    fn resolve_child(&self, model: &Model) -> Option<RouteId> {
        (self.child)(model)
    }
    fn occludes(&self) -> Occlusion {
        Occlusion::View
    }
    fn update(&mut self, _: &Model, _: &mut Outbox<Action>, _: f32) {}
    fn escape(&mut self, _: &Model, _: &mut Outbox<Action>) -> Escape {
        match self.id {
            "home" => Escape::Fall,
            _ => Escape::Pop,
        }
    }
    fn take_leave_request(&mut self) -> bool {
        false
    }
    fn child_popped(&mut self, out: &mut Outbox<Action>, _: RouteId) {
        out.push(Action::Closed { parent: self.id });
    }
    fn enter(&mut self, _: &mut Model) {}
    fn leave(&mut self, _: &mut Model) {}
    fn depart(&mut self, _: Option<RouteId>) -> Departure {
        Departure::Cut
    }
}
```

`resolve_child` is the important method: each frame, every node on the path names at most one child. `child_popped` is how a pop becomes an action that stops the parent claiming its child.

## 5. Assemble the app

```rust
type Screens = Router<Model, Action, dyn Node<Model, Action>>;

struct App {
    model: Model,
    router: Screens,
    store: Store,
    outbox: Outbox<Action>,
    editor: Producer<Editor>,
}

impl App {
    fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let table = table();
        let mut store = Store::over(&table);
        store.provides::<Editor>(EDITOR)?;
        let editor = store.producer::<Editor>(EDITOR, &["body", "words"])?;

        let routes: Vec<(RouteId, Box<dyn Node<Model, Action>>)> = vec![
            ("home", Box::new(Screen { id: "home", child: |m| m.home_child })),
            ("note", Box::new(Screen { id: "note", child: |m| m.note_child })),
            ("settings", Box::new(Screen { id: "settings", child: |_| None })),
        ];
        let router = Router::new(table, routes, |_: &Model| "home")?;

        let model = Model {
            notes: vec!["Welcome".into(), "Shopping\nbread\nleeks".into()],
            open: None,
            home_child: None,
            note_child: None,
        };
        let mut app = Self { model, router, store, outbox: Outbox::new(), editor };
        app.settle();
        Ok(app)
    }

    /// Apply what was asked for, then re-derive the path.
    fn settle(&mut self) -> bool {
        let asked = !self.outbox.is_empty();
        self.tick();
        let moved = self.router.resolve(&mut self.model, &mut self.store);
        if moved {
            self.tick(); // fill scopes that just mounted
        }
        asked || moved
    }

    fn tick(&mut self) {
        let Self { model, store, outbox, editor, .. } = self;
        store.tick(outbox, |actions, barrier| {
            for action in actions {
                model.apply(action);
            }
            // Refused while `note` is off the path: the scope does not exist.
            if let Ok(mut w) = barrier.writer(editor) {
                let body = model.open.map(|i| model.notes[i].clone()).unwrap_or_default();
                set!(w, words, body.split_whitespace().count()).unwrap();
                set!(w, body, body).unwrap();
            }
        });
    }
}
```

Every mistake in the table or the store setup (a typo'd parent, a scope on an unregistered id, two producers claiming one field) fails here, at startup, with a message that names it.

## 6. Draw and run

```rust
impl eframe::App for App {
    fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
        let mut back = ctx.input(|i| i.key_pressed(egui::Key::Escape));

        for id in self.router.visible_views() {
            egui::CentralPanel::default().show(ctx, |ui| match id {
                "home" => {
                    ui.heading("Notes");
                    for (i, note) in self.model.notes.iter().enumerate() {
                        if ui.link(note.lines().next().unwrap_or("Untitled")).clicked() {
                            self.outbox.push(Action::Open(i));
                        }
                    }
                    if ui.button("Settings").clicked() {
                        self.outbox.push(Action::OpenSettings { from: "home" });
                    }
                }
                "note" => {
                    if let Some(editor) = self.store.read::<Editor>(EDITOR) {
                        let mut body = editor.body.clone();
                        if ui.text_edit_multiline(&mut body).changed() {
                            self.outbox.push(Action::Edit(body));
                        }
                        ui.label(format!("{} words", editor.words));
                    }
                    if ui.button("Settings").clicked() {
                        self.outbox.push(Action::OpenSettings { from: "note" });
                    }
                    if ui.button("Close").clicked() {
                        back = true;
                    }
                }
                "settings" => {
                    ui.heading("Settings");
                    if ui.button("Back").clicked() {
                        back = true;
                    }
                }
                _ => {}
            });
        }

        if back {
            self.router.escape(&self.model, &mut self.outbox);
        }
        if self.settle() {
            ctx.request_repaint();
        }
    }
}

fn main() -> eframe::Result {
    let app = App::new().expect("the table and the store are valid");
    eframe::run_native(
        "notes",
        eframe::NativeOptions::default(),
        Box::new(|_| Ok(Box::new(app))),
    )
}
```

The home list reads the model directly to keep this short. The full example puts the list in a process-wide scope too, so every view draws from the store.

## What just happened

- The path was never pushed or popped. It was derived from `home_child` and `note_child` each frame.
- Escape went to the deepest screen first. `settings` popped, its parent pushed `Closed`, the next tick applied it, and the next walk shortened the path.
- The `Editor` scope appeared when `note` joined the path and vanished when it left. Nothing tore it down by hand.

## Next

- Learn why the frame runs in this order in [The frame](/docs/the-frame).
- Read the full example: `examples/notes.rs` adds a guarded trash, an unsaved-changes prompt that stacks over the editor, and a subscriber.
