# `Noizu.Tessera.Spec`
[🔗](https://github.com/the-robot-lives/tessera/blob/v0.2.0/lib/noizu/tessera/spec.ex#L1)

The compiled spec of a tessera component (CONTRACT.md is authoritative).

A spec is built at compile time by `use Noizu.Tessera.Component` from the
`attr`/`prop`/`event`/`slot`/`cy` declarations of the component module, and
is exposed on that module as `__tessera_spec__/0`. It is the single source
the generated JS contract module (`Noizu.Tessera.Codegen.contract_module/1`),
the Custom Elements Manifest (`Noizu.Tessera.Codegen.cem/1`) and the
Hologram adapter all derive from.

Entry shapes (plain maps):

    attrs:   %{name: :display_name, type: :string, required: true, default: nil, line: 3}
             optional: attribute: "custom-name" | false, reflect: true
    props:   %{name: :tag_list, type: :list, json: true, default: [], line: 4}
    events:  %{name: "persona-select", detail: %{persona_id: :string}, line: 5}
    slots:   %{name: "default", line: 6}
    styles:  %{kind: :css, value: ":host { … }", line: 7}
             %{kind: :shared, value: "./shared_styles.mjs", line: 8}
    css:     %{name: "--persona-card-accent", default: "#6366f1", line: 7}
    parts:   %{name: "select-button", line: 8}
    lifecycle: %{will_update: %{js: "…", line: 9}, …}  (only declared hooks)

`css`/`part` entries are documentation metadata for the Custom Elements
Manifest (and the gallery) — they do not ride the wire contract.

# `action_entry`

```elixir
@type action_entry() :: %{
  :name =&gt; atom(),
  :js =&gt; String.t(),
  optional(:fn_ast) =&gt; Macro.t(),
  optional(:inherited) =&gt; boolean(),
  optional(:internal) =&gt; boolean(),
  optional(:re_emit) =&gt; boolean(),
  optional(:line) =&gt; non_neg_integer()
}
```

# `attr_entry`

```elixir
@type attr_entry() :: %{
  :name =&gt; Noizu.Tessera.Naming.snake(),
  :type =&gt; atom(),
  optional(:required) =&gt; boolean(),
  optional(:default) =&gt; any(),
  optional(:attribute) =&gt; String.t() | false,
  optional(:reflect) =&gt; boolean(),
  optional(:line) =&gt; non_neg_integer()
}
```

# `css_entry`

```elixir
@type css_entry() :: %{
  :name =&gt; String.t(),
  optional(:default) =&gt; String.t() | nil,
  optional(:line) =&gt; non_neg_integer()
}
```

# `event_entry`

```elixir
@type event_entry() :: %{
  :name =&gt; Noizu.Tessera.Naming.kebab(),
  optional(:detail) =&gt; %{required(Noizu.Tessera.Naming.snake()) =&gt; atom()},
  optional(:line) =&gt; non_neg_integer(),
  optional(:inherited) =&gt; boolean(),
  optional(:renames) =&gt; Noizu.Tessera.Naming.kebab(),
  optional(:renamed_from) =&gt; Noizu.Tessera.Naming.kebab()
}
```

# `lifecycle`

```elixir
@type lifecycle() :: %{optional(lifecycle_hook()) =&gt; lifecycle_entry()}
```

# `lifecycle_entry`

```elixir
@type lifecycle_entry() :: %{:js =&gt; String.t(), optional(:line) =&gt; non_neg_integer()}
```

# `lifecycle_hook`

```elixir
@type lifecycle_hook() :: :should_update | :will_update | :first_updated | :updated
```

# `part_entry`

```elixir
@type part_entry() :: %{:name =&gt; String.t(), optional(:line) =&gt; non_neg_integer()}
```

# `prop_entry`

```elixir
@type prop_entry() :: %{
  :name =&gt; Noizu.Tessera.Naming.snake(),
  :type =&gt; atom(),
  optional(:json) =&gt; boolean(),
  optional(:default) =&gt; any(),
  optional(:attribute) =&gt; String.t() | false,
  optional(:reflect) =&gt; boolean(),
  optional(:line) =&gt; non_neg_integer()
}
```

# `region_entry`

```elixir
@type region_entry() :: %{name: atom(), nested: [atom()]}
```

# `region_method`

```elixir
@type region_method() :: %{name: atom(), js: String.t()}
```

# `slot_entry`

```elixir
@type slot_entry() :: %{:name =&gt; String.t(), optional(:line) =&gt; non_neg_integer()}
```

# `state_entry`

```elixir
@type state_entry() :: %{
  :name =&gt; Noizu.Tessera.Naming.snake(),
  :type =&gt; atom(),
  optional(:default) =&gt; any(),
  optional(:attribute) =&gt; String.t() | false,
  optional(:reflect) =&gt; boolean(),
  optional(:line) =&gt; non_neg_integer()
}
```

# `style_entry`

```elixir
@type style_entry() :: %{
  :kind =&gt; :css | :shared,
  :value =&gt; String.t(),
  optional(:line) =&gt; non_neg_integer()
}
```

# `t`

```elixir
@type t() :: %Noizu.Tessera.Spec{
  actions: [action_entry()],
  attrs: [attr_entry()],
  css_properties: [css_entry()],
  cy: String.t() | nil,
  derived_from: module() | nil,
  events: [event_entry()],
  extension_api: String.t() | nil,
  file: String.t() | nil,
  form_associated: boolean(),
  js: String.t() | nil,
  lifecycle: lifecycle(),
  module: module() | nil,
  parts: [part_entry()],
  priority: integer(),
  props: [prop_entry()],
  region_overrides: [atom()],
  regions: [region_entry()],
  slots: [slot_entry()],
  state: [state_entry()],
  styles: [style_entry()],
  tag: Noizu.Tessera.Naming.kebab() | nil,
  template: template_entry() | nil,
  version: pos_integer()
}
```

# `template_entry`

```elixir
@type template_entry() :: %{
  :raw =&gt; String.t(),
  :lexer =&gt; :hologram | :minimal,
  :render_js =&gt; String.t(),
  optional(:line) =&gt; non_neg_integer(),
  optional(:regions) =&gt; [region_entry()],
  optional(:region_methods) =&gt; [region_method()],
  optional(:region_overrides) =&gt; [atom()]
}
```

# `default_extension_api`

```elixir
@spec default_extension_api() :: String.t()
```

The first stable extension_api used when a spec declares none (§8.1).

# `escape_json`

```elixir
@spec escape_json(String.t()) :: String.t()
```

Escapes a string for inclusion inside a JSON double-quoted literal. Every
control character (< 0x20) is escaped: LF/CR/TAB as the short forms, all
others (backspace, form feed, NUL, ESC, ...) as `\u00XX` — a raw control
byte would be a SyntaxError in the generated `.mjs` and break `JSON.parse`.

# `json_literal`

```elixir
@spec json_literal(any()) :: String.t()
```

The JSON-literal form of a spec default value, as it appears in the
generated contract module (CONTRACT.md §2). `nil` becomes `null`.

# `to_manifest_map`

```elixir
@spec to_manifest_map(t()) :: map()
```

Spec rendered to a plain JSON-encodable map (used by `__tessera_manifest__/0`).

# `validate!`

```elixir
@spec validate!(
  atom(),
  String.t() | atom(),
  keyword() | %{required(atom()) =&gt; atom()},
  String.t(),
  non_neg_integer()
) :: :ok
```

Validates an option value against the contract and raises `CompileError`
with a pointed message otherwise. Used at compile time by
`Noizu.Tessera.Component` and by `mix tessera.gen.component`.

`kind` is the DSL entry being validated (`"attr"`, `"prop"`, `"event"`, ...).

# `validate_type!`

```elixir
@spec validate_type!(
  String.t() | atom(),
  atom(),
  atom(),
  String.t(),
  non_neg_integer()
) :: :ok
```

Validates the DSL kind + type carried in a raw entry tuple.

---

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