# Build a terminal UI with Ito

Build the application I describe using Ito, a Lua library for terminal UIs.
Return one complete `main.lua` block for the playground. Use `require("ito")`.
The browser runs the same terminal layout, drawing, state, and input handling
in WebAssembly and paints the resulting cells on Canvas. A fixed host theme
supplies the colors and font. Do not produce HTML, CSS, JavaScript, CSS classes,
or browser widgets.

## A complete example

```lua
local ito = require("ito")
local phase = ito.state("edit")
local name = ito.state("")
local saved = ito.state("")

return function()
  if phase.value == "edit" then
    return ito.VStack({
      ito.Text("NAME YOUR PROJECT"):bold(),
      ito.TextField(name):label("Project name"):focused()
        :on_submit(function(value)
          if value:match("%S") then
            saved.value = value
            phase.value = "done"
          end
        end)
        :on_cancel(function() name.value = "" end),
      ito.Text("Enter to save · Esc to clear"):dim():wrap(),
    }):spacing(1):padding(1)
  end
  return ito.VStack({
    ito.Text("Saved: " .. saved.value):bold():wrap(),
    ito.Button("Edit again", function() phase.value = "edit" end):focused(),
  }):spacing(1):padding(1)
end
```

The playground owns `ito.open()`, the window, theme, and event loop. Return a
function that builds one view. A local host uses that same view function with
`ito.Window`, calls `window:event(event)` for terminal events, and renders again
after input. Use this branch's current Ito build for the shared control additions.
Do not open another terminal or implement a blocking loop in playground code.

## Input and state

- The active control receives input. `TextField(state)` edits a Lua buffer and
  draws an inline caret. It is not an independent HTML text box.
- `:focused()` requests focus when a control appears. `:focused(condition)`
  requests it when the condition becomes true. Stable focus survives redraws.
  Pointer clicks focus a control. Tab and Shift-Tab move between controls when
  the active control does not consume them. At the boundary the browser can
  leave the preview. Only the active text field displays a caret.
- `TextField:on_submit(fn)` handles Enter; `:on_cancel(fn)` handles Escape.
  Both receive the current string. `:on_change(fn)` receives actual text changes.
  Cancel does not automatically roll back a value. Keep a draft separate from
  the saved value when cancelling should discard edits.
- Use a state such as `phase = ito.state("choose")` and conditional views for
  choose → edit → review → done. Add `:id(phase.value)` if each phase should get
  fresh internal view state. Keep application state above the returned function.
- `:on_key(function(event) ... end)` handles keys the focused control leaves
  unhandled. Return `true` when consumed, `false` otherwise. `event.key` uses
  `enter`, `esc`, `tab`, `backtab`, `up`, `down`, `left`, `right`, `home`, `end`,
  `backspace`, or a character. Modifiers are `ctrl`, `alt`, `shift`, and `meta`.
  Paste is one `{ type = "paste", text = "..." }` event. Do not submit pasted
  newlines as Enter. The single-line editor turns them into spaces.
- `ito.state(value)` reads/writes through `.value`. Read state while building
  the view; update it in actions. Mutating a plain table does not notify Ito.
  Replace a state value or increment a revision state for mutable collections.
- `ito.view(function(props) ... end)` defines a composed view. Calls to state
  and `ito.remember(function() ... end)` inside it must keep a consistent order.
  Use unique `:id(key)` values for changing lists. A `false` child is omitted.

## Views and controls

| API | Purpose |
| --- | --- |
| `Text(string)` | Terminal text. Use `tostring` for numbers. Also accepts `{ { text, TextStyle(...) }, ... }` spans. |
| `VStack({ views })`, `HStack({ views })` | Vertical/horizontal layout; `:spacing(1)` adds one cell or row. |
| `TextField(state)` | Single-line editing; `:label(text)`, `:placeholder(text)`, `:hidden()` for passwords. |
| `Button(text, action)` | Enter, Space, or pointer activation. `action()` takes no arguments. |
| `Checkbox(text, boolState)`, `Toggle(text, boolState)` | Terminal boolean controls. `:description(text)` and `:on_change(fn)`. |
| `Slider(numberState, { min = 0, max = 100, step = 1 })` | Arrow keys adjust; Home/End jump to bounds. `:label(text)`, `:suffix(text)`, `:on_change(fn)`. |
| `Progress(fraction)` | A terminal meter, clamped to 0–1. `:label(text)` is optional. |
| `List(items, function(item, index, active) ... end)` | Navigable rows. `:selection(numberState)`, `:on_choose(function(item, index) ... end)`, `:height(rows)`. |
| `Choices({ { label = "...", description = "..." }, ... })` | Numbered choices plus a text answer. `:on_choose(fn)`, `:on_other(fn)`. |
| `ScrollView(view):state(ScrollState()):height(rows)` | A bounded scroll area. Wheel input uses terminal coordinates. |
| `Grid({ GridRow({ views }), ... })`, `Group({ views })`, `ZStack({ views })`, `Spacer()` | Shared terminal composition and layout. |
| `Heading(text)`, `Metric(value)` | Convenience emphasized text, not browser-sized typography. |

All names above are on `ito`. Button, checkbox, toggle, and slider accept
`:disabled(condition)`. Disabled and invisible controls do not receive focus.

## Layout and appearance

Use `:padding(1)`, `:padding({ horizontal = 2 })`, `:width(cells)`,
`:height(rows)`, `:max_height(rows)`, `:grow()`, `:share(0.5)`,
`:align(ito.Alignment.center)`, `:id(key)`, and `:invisible(condition)`.
`:hidden(condition)` removes a view from layout; TextField's `:hidden()` instead
masks its text. Prefer flexible widths and `Text(...):wrap()` for small screens.
The playground allows up to 160 columns and 120 rows. Use scrolling for more.

Text supports `:bold()`, `:dim()`, `:italic()`, `:underline()`,
`:strikethrough()`, `:reverse()`, `:foreground(ito.rgb(0xc65036))`, and
`:style(ito.TextStyle({ bold = true }))`. `:background(ito.rgb(...))` fills a
view. A border uses `:border(ito.theme().borders.rounded)` or `.plain`;
`:title(text)` labels it. Read the host theme inside the view function.
Padding before a border/background is inside; padding after is outside.
`:accessibility_label(text)` names a control without changing its drawing.

The browser supplies keyboard, pointer, paste, and IME input. It does not add
DOM, networking, filesystem, or raw ANSI output APIs. Canvas includes a text
fallback, but does not reproduce the accessibility of individual native HTML
controls. Lua has a memory limit and instruction budget. Endless loops stop.
Valid edits restart the app; invalid edits keep the last working app usable.

Check keyboard navigation, submit/cancel transitions, and narrow widths. Keep
repeated values such as prices in one variable. Do not invent methods like
`:class()`, `:css()`, or `:font_size()`.

**What I want to build:** [Add my idea here.]
