# Intents and the outbox

> How screens and views ask for changes, and how a named request becomes a typed action.

Nothing that runs during a frame can change your model directly. Views, nodes and Escape handlers can only ask. They ask by pushing actions onto an outbox, which the next tick drains in order and hands to the one place that applies them.

## The outbox

```rust
use carte::Outbox;

let mut out: Outbox<Action> = Outbox::new();
out.push(Action::Save);
out.push(Action::Close);
```

`Outbox<A>` is a queue of your action type, in the order things were asked for. It is not a channel. Actions are pushed during a frame and drained at a known point in that same frame, inside `Store::tick`, so nothing crosses frames and nothing needs synchronising. One queue, drained once per frame, means two parts of your app can never each drain it and drop what the other wanted.

| Method | Does |
| --- | --- |
| `push(action)` | Ask for something. Order is kept |
| `drain()` | Take everything, leaving it empty. `Store::tick` calls this |
| `peek()` | Look without draining, for tests |
| `len()`, `is_empty()` | Count |

Node methods that run during a frame (`update`, `escape`, `child_popped`) receive the outbox and only `&` your model. That is what makes "a screen can only ask" a rule the compiler checks.

## Typed actions from your UI

If your UI is Rust, push your action enum directly, as the [Quick start](/docs/quick-start) does. That is all most apps need.

## Named raises

Sometimes the request arrives by name rather than as a Rust value: from a UI language, a script, a plugin, or anything else that can only say `"rename"` and pass a few arguments. Carte's intents turn such a raise into a typed action, checked against what the scope says it accepts.

A scope publishes the intents it accepts the same way it publishes the fields it provides: as a Rust type, here an enum implementing `Intents`.

```rust
use carte::{Accepted, Intents, Parameter, Raise, RaiseArg, Refused, Vocabulary};

#[derive(Debug)]
enum NoteIntent {
    Save,
    Rename { title: String },
}

impl Intents for NoteIntent {
    fn vocabulary() -> Vocabulary {
        Vocabulary::new(vec![
            Accepted::new("save", vec![]),
            Accepted::new("rename", vec![Parameter::of::<String>("title")]),
        ])
    }
    fn accept(raise: &Raise) -> Result<Self, Refused> {
        match raise.name() {
            "save" => {
                raise.parameters("save", 0)?;
                Ok(NoteIntent::Save)
            }
            "rename" => {
                let mut p = raise.parameters("rename", 1)?;
                Ok(NoteIntent::Rename { title: p.take::<String>("title")? })
            }
            other => Err(Refused::NoSuchIntent { intent: other.to_string() }),
        }
    }
}
```

> **No derive yet.** A derive writes `vocabulary` and `accept` from the enum, so the two cannot disagree. Like the `Schema` derive, it lives in a separate, unpublished crate. Until it moves into Carte you write both by hand, as above, and you are responsible for keeping them in step.

The `Raise` API is shaped so a decoder cannot go wrong in the usual ways:

- A raise offers only its `name()` until you name the intent. Its arguments open with `parameters(intent, arity)`, which checks the count first. You cannot branch on an argument to decide which intent it is.
- `Args::take::<T>(name)` reads the next argument as the parameter's type. There is no index, so you cannot read one argument twice.
- `T` must implement `Argument`: `String`, `bool`, and the integer and float types. An integer argument widens into a float. A float never narrows into an integer.

A raise carries what a click knew: an id, a count, a flag. Anything richer is a fact, and facts travel the other way, through the store.

## Accepting and raising

```rust
store.accepts::<NoteIntent>(EDITOR)?;

let raise = Raise::new("rename", vec![RaiseArg::Str("Groceries".into())]);
store.raise::<NoteIntent, NoteIntent>(EDITOR, &raise, &mut out)?;
```

`Store::accepts` publishes the vocabulary for a scope. One scope has one vocabulary (`StoreError::AlreadyAccepting`), and a vocabulary with a name declared twice is refused (`StoreError::DuplicateIntent`). Providing fields and accepting intents are independent: a scope may do either or both.

`Store::raise::<I, A>` checks the raise against the scope's vocabulary, decodes it with `I::accept`, converts it with `A::from`, and pushes it onto the outbox. If your action enum is your intent enum, `A` is `I`. If it is wider, write one `From<NoteIntent> for Action` impl. A refusal is a `RaiseError` whose `scope()` and `refusal()` say where and why.

Because the result goes on the outbox, it is applied in the next tick before producers run, so its effect shows in the same frame.

`RaiseArg` is the argument type: `Str`, `Int`, `Float`, `Bool`, or `Opaque` for something a host cannot act on, such as a closure.

## Checking a vocabulary

`Store::intents(scope)` returns a scope's `Vocabulary`. `Vocabulary::check(&declared)` compares it with what a UI document says it raises, given as a list of `Declared { name, parameters }`, and returns every `Drift`:

| Drift | Grade |
| --- | --- |
| `Unaccepted` | Refuses: the document raises an intent the scope does not accept |
| `Arity` | Refuses: wrong number of arguments |
| `Parameter` | Refuses: an argument of the wrong shape, named |
| `Unraised` | Reports: the scope accepts an intent nothing raises |

`Drift::refuses()` is the line between the grades. For checking many documents against many scopes, see [Validation](/docs/validation).

## Next

[Validation](/docs/validation): checking what views read and raise against what the store publishes.
