# `Espex.Wizard.Document`
[🔗](https://github.com/bbangert/espex/blob/main/lib/espex/wizard/document.ex#L1)

The device wizard: the pages Home Assistant shows when the device is
added. Build it with `new/1` and return it from `c:Espex.Wizard.wizard/0`.

    alias Espex.Wizard.{Document, Entity, EntityFilter, Input, Page}

    Document.new!(
      pages: [
        %Page{
          title: "Calendars",
          description: "Pick the calendars to show on the display.",
          inputs: [
            %Input{
              id: "calendars",
              multiple: true,
              entity_filters: [%EntityFilter{domain: ["calendar"]}]
            }
          ]
        },
        %Page{
          title: "Backlight",
          entities: [%Entity{key: 1001, description: "Turn it on to test the display."}]
        }
      ]
    )

`new/1` checks the document against ESPHome's limits, so a mistake
shows up when the host builds it rather than when a client connects:

  * at least one page, and each page has an entity or an input
  * page titles up to 127 bytes, descriptions up to 255
  * each filter sets at least one field; filter strings are 1-63 bytes,
    `supported_features` entries 1-127
  * `max_length` is 255-4095 and only on a `multiple: true` input
  * `links` only on an input without `multiple`, and each without an
    `entity_id` (see "Linked inputs" in `Espex.Wizard.Input`)
  * input ids are unique and their hashes do not collide
  * the compressed document fits one API message (65511 bytes)

It also fills each input's `key` from its `id`, and turns each link
into an `Espex.HomeAssistantStates.Subscription`. Links stay on the
device: the JSON sent to the client does not carry them. A `%Document{}` built
as a struct without `new/1` is run through it when a connection is
accepted.

Strings are passed to Home Assistant verbatim, so they may be
translation placeholders such as `"[%key:component::light::title%]"`.

On the wire the document is compact UTF-8 JSON, version 1, compressed
into one zstd frame. Empty and unset values are left out. Building it
needs OTP 28 or newer for `:zstd`.

# `t`
*since 0.12.0* 

```elixir
@type t() :: %Espex.Wizard.Document{
  blob: binary() | nil,
  pages: [Espex.Wizard.Page.t()]
}
```

# `new`
*since 0.12.0* 

```elixir
@spec new(keyword()) :: {:ok, t()} | {:error, String.t()}
```

Build and check a document from `pages:`, a list of `Espex.Wizard.Page`
structs. Returns `{:error, message}` naming the first problem.

# `new!`
*since 0.12.0* 

```elixir
@spec new!(keyword()) :: t()
```

Like `new/1`, but raises `ArgumentError` on a problem.

# `to_blob`
*since 0.12.0* 

```elixir
@spec to_blob(t()) :: binary()
```

The document as sent in `DeviceWizardResponse.data`: compact JSON in
one zstd frame. Built once by `new/1`.

---

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