# `Tela`
[🔗](https://github.com/idabmat/tela/blob/v0.1.0/lib/tela.ex#L1)

Zero-dependency Elixir TUI framework using the Elm Architecture.

## Overview

Tela lets you build interactive terminal applications with a simple,
testable callback interface modelled on `GenServer` and `Phoenix.LiveView`.
The runtime handles raw mode, alternate screen, rendering, and cleanup —
your callbacks stay pure and easy to unit-test.

## Quickstart

    defmodule MyApp do
      use Tela

      @impl Tela
      def init(_args), do: {%{count: 0}, nil}

      @impl Tela
      def handle_event(model, %Tela.Key{key: {:char, "q"}}), do: {model, :quit}
      def handle_event(model, %Tela.Key{key: {:char, " "}}), do: {%{model | count: model.count + 1}, nil}
      def handle_event(model, _key), do: {model, nil}

      @impl Tela
      def handle_info(model, _msg), do: {model, nil}

      @impl Tela
      def view(model), do: Tela.Frame.new("Count: #{model.count}\nPress <space> to increment, q to quit.")
    end

      {:ok, _model} = MyApp.run()

## Callbacks

All four callbacks are pure functions. You can call them directly in your
test suite without starting the runtime.

### `c:init/1`

Called once with the args passed to `run/2`. Returns `{initial_model, cmd}`.

### `c:handle_event/2`

Called for every parsed keystroke. Receives the current model and a
`Tela.Key` struct. Returns `{new_model, cmd}`.

### `c:handle_info/2`

Called for task results and any other messages sent to the runtime process.
Returns `{new_model, cmd}`.

### `c:view/1`

Called after every state change. Returns a `Tela.Frame` with the full UI
string (lines separated by `\n`) and an optional cursor position. The
runtime diffs against the previous frame and only redraws changed lines,
then positions the real terminal cursor if one is specified.

## Commands

The `cmd` returned from `handle_event/2` and `handle_info/2` can be:

- `nil` — no side effect.
- `:quit` — stop the runtime and restore the terminal.
- `{:task, fun}` — run `fun/0` in a new process. The return value is
  sent back to the runtime as a message and delivered to `handle_info/2`.

## `use Tela`

`use Tela` injects the `@behaviour` declaration and a `run/1` convenience
function into your module. You still define all four callbacks yourself.

# `cmd`

```elixir
@type cmd() :: nil | :quit | {:task, (-&gt; term())}
```

A command returned from `handle_event/2` or `handle_info/2`.

- `nil` — no side effect.
- `:quit` — stop the runtime cleanly.
- `{:task, fun}` — spawn `fun` in a new process; the return value is
  delivered to `handle_info/2` as `{:task_result, result}`.

# `handle_event`

```elixir
@callback handle_event(model :: term(), key :: Tela.Key.t()) :: {term(), cmd()}
```

Called for every parsed keystroke from stdin.

Returns `{new_model, cmd}`. Must be a pure function.

# `handle_info`

```elixir
@callback handle_info(model :: term(), msg :: term()) :: {term(), cmd()}
```

Called for task results and any other messages delivered to the runtime
process.

Returns `{new_model, cmd}`. Must be a pure function.

# `init`

```elixir
@callback init(args :: term()) :: {model :: term(), cmd()}
```

Called once at startup. Returns `{initial_model, cmd}`.

`cmd` is executed immediately after the first render — use `{:task, fun}`
to kick off background work (e.g. a spinner tick) or `nil` for no side
effect. Must not perform I/O directly.

# `view`

```elixir
@callback view(model :: term()) :: Tela.Frame.t()
```

Called after every state change. Returns a `Tela.Frame` containing the full
UI string (lines separated by `\n`) and an optional cursor position.

Set `cursor:` to `{row, col, shape}` (0-indexed, absolute within the full
frame) to show the real terminal cursor at that position. Set to `nil` (or
use `Frame.new/1`) to hide the cursor. Use `Tela.Frame.join/2` to compose
multiple component frames — it handles cursor row offset arithmetic
automatically.

Must be a pure function.

# `run`

```elixir
@spec run(module :: module(), args :: term()) :: {:ok, term()}
```

Starts the Tela runtime for the given module, passing `args` to `init/1`.

Blocks until the application quits (via a `:quit` cmd or process exit).
The terminal is always restored on exit. Returns `{:ok, final_model}` where
`final_model` is the model at the time the application quit.

## Examples

    {:ok, model} = Tela.run(MyApp, [])
    {:ok, model} = Tela.run(MyApp, %{initial_tab: :tasks})

---

*Consult [api-reference.md](api-reference.md) for complete listing*
