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

Naming conversions of the tessera wire contract (CONTRACT.md §1).

| Elixir (spec)            | DOM / JS                    | Rule |
|--------------------------|-----------------------------|------|
| `attr :display_name`     | attribute `display-name`    | snake → kebab |
| `prop :tag_list`         | property `tagList`          | snake → camel |
| `event "persona-select"` | CustomEvent `persona-select`| kebab, verbatim |
| `event "persona-select"` | binding prop `on_persona_select` | `"on_" <> snake` |
| `event "persona-select"` | default action `:persona_select` | `snake(event)` |
| detail key `persona_id`  | detail key `personaId`      | snake → camel |

# `camel`

```elixir
@type camel() :: String.t()
```

A camelCase DOM name, e.g. `"displayName"`.

# `kebab`

```elixir
@type kebab() :: String.t()
```

A kebab-case DOM name, e.g. `"display-name"`.

# `snake`

```elixir
@type snake() :: atom()
```

A snake_case identifier, e.g. `:display_name`.

# `camel_to_snake`

```elixir
@spec camel_to_snake(camel()) :: snake()
```

Converts a camelCase key back to snake_case — mirrors the JS core's
`snakeKeysDeep`: EVERY uppercase char becomes `"_"` + its lowercase
(`userID` → `user_i_d`), byte-exact with `priv/js/src/events.mjs`.

# `event_binding_prop`

```elixir
@spec event_binding_prop(kebab()) :: snake()
```

Binding-prop name for an event: `"on_" <> snake(event)` (CONTRACT.md §1).

# `event_default_action`

```elixir
@spec event_default_action(kebab()) :: snake()
```

Default action atom for an event: `snake(event)` (CONTRACT.md §1).

# `event_regex`

```elixir
@spec event_regex() :: Regex.t()
```

Event-name grammar (CONTRACT.md §1): kebab-case, at least one hyphen.

# `kebab_to_snake`

```elixir
@spec kebab_to_snake(kebab()) :: snake()
```

kebab-case event/tag name → snake_case atom.

# `known_type?`

```elixir
@spec known_type?(atom()) :: boolean()
```

True when the given atom is a contract type.

# `known_types`

```elixir
@spec known_types() :: [atom()]
```

Wire-contract value types (CONTRACT.md §2).

# `round_trips?`

```elixir
@spec round_trips?(snake()) :: boolean()
```

True when a snake name survives the wire round-trip:
`snake |> snake_to_camel |> camel_to_snake == snake`.

The JS core and the Elixir bridge rely on this equivalence; names that do
not round-trip would silently change meaning across the bridge. Fails for
digit-initial segments (`col_2_width` → `col2Width` → `col2_width`) and
for double/trailing underscores (`foo__bar`, `foo_`).

# `snake_regex`

```elixir
@spec snake_regex() :: Regex.t()
```

Spec identifier grammar for attr/prop/detail names: snake_case atoms.

# `snake_to_camel`

```elixir
@spec snake_to_camel(snake()) :: camel()
```

snake_case atom → camelCase DOM property / detail key.

BYTE-EXACT with the JS core (`priv/js/src/events.mjs`): split the snake
string on `"_"` and capitalize the first letter of every segment after the
first (`persona_id` → `personaId`). Note this deliberately DIFFERS from
`Macro.camelize/1`/`Macro.underscore/1` for acronyms/digits
(`Macro.underscore("userID")` = `user_id`, but the JS rule gives
`user_i_d`) — never use Macro.* for contract keys.

# `snake_to_kebab`

```elixir
@spec snake_to_kebab(snake()) :: kebab()
```

snake_case atom → kebab-case DOM attribute name.

# `tag_regex`

```elixir
@spec tag_regex() :: Regex.t()
```

Custom-element tag grammar (CONTRACT.md §1): lowercase with a hyphen.

---

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