painted — TUI Core Layers
Description: painted documentation

# TUI Core: Surface and Layers

Interactive painted apps are built from:

- `Surface`: owns the terminal lifecycle and buffer diff loop
- `Layer`: a modal stack primitive for input routing and render ordering

See also:

- `docs/ARCHITECTURE.md`: input/render flow diagrams (`../ARCHITECTURE.md`)
- `docs/DATA_PATTERNS.md`: frozen state + pure functions (`../DATA_PATTERNS.md`)

## Surface

`Surface` is the async event loop wrapper (alt-screen, keyboard/mouse input, dirty rendering, resize handling).

    class Surface:

        async def run(self) -> None:

        def handle_key(
            self,
            key: str,
            state: Any,
            get_layers: Callable[[Any], tuple[Layer, ...]],
            set_layers: Callable[[Any, tuple[Layer, ...]], Any],
        ) -> tuple[Any, bool, Any]:

## Layer stack

Layers model “modal scopes” (help overlay, search overlay, confirmation dialog). Input routes top-down; render paints bottom-up.

    @dataclass(frozen=True, slots=True)
    class Layer(Generic[S]):
        """A layer bundles state, input handling, and rendering for modal stacking.

        - state: layer-local state, created on push, gone on pop
        - handle: (key, layer_state, app_state) -> (layer_state, app_state, action)
        - render: (layer_state, app_state, buffer_view) -> None
        """

        name: str
        state: S
        handle: Callable[[str, S, Any], tuple[S, Any, Action]]
        render: Callable[[S, Any, BufferView], None]

    def process_key(
        key: str,
        state: A,
        get_layers: Callable[[A], tuple[Layer, ...]],
        set_layers: Callable[[A, tuple[Layer, ...]], A],
    ) -> tuple[A, bool, Any]:

    def render_layers(
        state: A,
        buf: Buffer,
        get_layers: Callable[[A], tuple[Layer, ...]],
    ) -> None:

## Pattern: app owns state, layers are pure

The intended shape:

- App state: immutable dataclass containing all UI state (including `layers`)
- Layers: `(key, layer_state, app_state) -> (layer_state, app_state, action)`
- Rendering: layer paints into a `BufferView` derived from the app buffer

This keeps “what the UI is” (state) separate from “how it runs” (Surface) and “how it routes” (layers).