README.md

# ex_a2ui

Lightweight Elixir library for Google's [A2UI](https://a2ui.org/) protocol. Serve interactive, agent-driven UI surfaces from any BEAM app via declarative JSON over WebSocket — no Phoenix or LiveView required.

**Status:** v0.3.0 — v0.9 wire format, push updates, WebSocket server. See [ROADMAP.md](ROADMAP.md) for the full plan.

**A2UI spec:** v0.9

## Why Elixir for A2UI?

A2UI is a protocol where servers produce declarative JSON describing UI, and clients render it natively. The BEAM VM is a natural fit:

- **One process per connection** — each WebSocket client gets an isolated, lightweight process with its own state. No shared mutable state, millions of concurrent connections.
- **Fault tolerance** — one client's surface crashes, others are unaffected. The supervisor restarts it.
- **Real-time push** — broadcasting state changes to connected clients is what the BEAM was built for.
- **AI agent UIs** — LLMs generate flat JSON component lists, Elixir manages the stateful conversation loop, clients render natively.

## When to use this vs Phoenix LiveView

| | Phoenix LiveView | ex_a2ui |
|--|-----------------|---------|
| Rendering | Server-rendered HTML diffs | Client-rendered native widgets from JSON |
| Clients | Browser only | Browser, Flutter, Angular, React — any A2UI renderer |
| Dependencies | ~12+ packages (full web framework) | ~4 packages (protocol library) |
| AI/LLM friendly | No (HTML templates) | Yes (flat JSON designed for LLM generation) |
| Maturity | Battle-tested, huge ecosystem | Early, A2UI spec is v0.9 |

**Use LiveView** for full web applications. **Use ex_a2ui** for:

- **AI agent interfaces** — an LLM generates interactive UI as structured JSON, not HTML
- **Lightweight BEAM app UIs** — add a dashboard or config panel to an existing OTP app without pulling in Phoenix
- **Cross-platform from one server** — same Elixir backend serves browser (Lit), mobile (Flutter), desktop (Angular)

## Installation

```elixir
def deps do
  [
    {:ex_a2ui, "~> 0.3.0"}
  ]
end
```

## Quick start

Define a surface provider:

```elixir
defmodule MyApp.DashboardProvider do
  @behaviour A2UI.SurfaceProvider

  alias A2UI.Builder, as: UI

  @impl true
  def init(_opts), do: {:ok, %{count: 0}}

  @impl true
  def surface(state) do
    UI.surface("dashboard")
    |> UI.text("count", "Count: #{state.count}")
    |> UI.button("inc", "Increment", action: "increment")
    |> UI.card("main", children: ["count", "inc"])
    |> UI.root("main")
  end

  @impl true
  def handle_action(%A2UI.Action{name: "increment"}, state) do
    new_state = %{state | count: state.count + 1}
    {:reply, surface(new_state), new_state}
  end

  def handle_action(_action, state), do: {:noreply, state}
end
```

Start the server:

```elixir
# In your supervision tree:
children = [
  {A2UI.Server, provider: MyApp.DashboardProvider, port: 4000}
]
```

Open `http://localhost:4000` — the built-in debug renderer shows your surface and a message log.

## Try the demo

```bash
git clone https://github.com/23min/ex_a2ui.git
cd ex_a2ui
mix deps.get
mix run demo_server.exs
# Open http://localhost:4000
```

## What is A2UI?

A2UI (Agent-to-User Interface) is a Google protocol where AI agents generate interactive UI as declarative JSON. Instead of returning text or HTML, an agent describes UI components (buttons, cards, text fields) and the client renders them natively. User interactions flow back to the agent, creating a bidirectional loop.

```
Elixir app (server)          Browser/Mobile (client)
─────────────────           ─────────────────────
Build Surface structs  ──→  Receive JSON
Encode to A2UI JSON    ──→  Render native widgets
                       ←──  Send action JSON
Decode, update state   ──→  Receive updated surface
```

## API layers

### Builder (recommended)

Pipe-friendly functions for building surfaces:

```elixir
alias A2UI.Builder, as: UI

UI.surface("status")
|> UI.text("title", "Hello!")
|> UI.button("go", "Click Me", action: "do_thing")
|> UI.card("container", children: ["title", "go"])
```

### Structs (full control)

Direct protocol types for advanced use cases:

```elixir
%A2UI.Surface{
  id: "status",
  components: [
    %A2UI.Component{
      id: "title",
      type: :text,
      properties: %{text: %A2UI.BoundValue{literal: "Hello!"}},
      children: []
    }
  ]
}
```

Both layers produce the same structs. Use whichever fits your style.

## Standard components

| Category | Types |
|----------|-------|
| Layout | `:row`, `:column`, `:list` |
| Display | `:text`, `:image`, `:icon`, `:video`, `:divider` |
| Interactive | `:button`, `:text_field`, `:checkbox`, `:date_time_input`, `:slider`, `:choice_picker` |
| Media | `:audio_player` |
| Container | `:card`, `:tabs`, `:modal` |

Custom components are supported via `Builder.custom/4`:

```elixir
UI.custom(surface, :my_chart, "chart-1", data: bind("/chart/data"))
```

## Data binding

Components bind to a data model using JSON Pointer paths:

```elixir
# Bind a text component to a data model path
UI.text(surface, "name", bind: "/user/name")

# Set the data model value
|> UI.data("/user/name", "Alice")
```

When the data model updates, bound components update automatically.

## Project structure

```
lib/
  a2ui.ex                  # Public API, spec version
  a2ui/
    component.ex           # Component struct + 18 standard types
    surface.ex             # Surface (flat component list + data model)
    bound_value.ex         # Data binding (literal, path, or both)
    action.ex              # User interaction events
    encoder.ex             # Elixir structs → A2UI JSON wire format
    decoder.ex             # Incoming action JSON → Elixir structs
    builder.ex             # Pipe-friendly convenience API
    surface_provider.ex    # Behaviour for defining surfaces and handling actions
    socket.ex              # WebSock handler (A2UI message flow)
    endpoint.ex            # Plug endpoint (HTTP + WebSocket routing)
    supervisor.ex          # OTP Supervisor (Registry + Bandit)
    server.ex              # OTP supervision tree entry point, push API
priv/
  static/
    index.html             # Built-in debug renderer page
```

## Dependencies

Runtime: `jason`, `bandit`, `websock_adapter`

That's it. No Phoenix, no Ecto, no LiveView.

## Development

```bash
git config core.hooksPath .githooks   # one-time setup
mix ci                                # format + compile + test (100 tests)
```

## License

MIT — see [LICENSE](LICENSE).