# `PhoenixKitCatalogue.Extensions`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.44.2/lib/phoenix_kit_catalogue/extensions.ex#L1)

Discovery and absorption for `PhoenixKitCatalogue.Extension` implementers
— the item/category form "extension slot" (spec §2 principle 8, §4 row
C4).

Discovery is duck-typed, mirroring `PhoenixKitAI.Translatables`'
`ai_translatables/0` pattern: any module registered with
`PhoenixKit.ModuleRegistry` that exports `catalogue_extensions/0` gets
its returned modules folded in, filtered to those reporting
`enabled?/0`. Catalogue never references an implementer by name — an
unregistered or disabled host changes nothing (spec §4 row C4's
regression rule: forms without a registered extension render exactly as
before).

# `absorb`

```elixir
@spec absorb(:item | :category, map(), map()) ::
  {:ok, map()} | {:error, {module(), [{atom(), String.t()}]}}
```

Folds every enabled extension's submitted namespace into `data`.

For each enabled extension `E` exporting the `kind`-appropriate cast
callback: takes `params[E.key()]` (a map, or `%{}` when absent/nil),
calls `E.cast_item/2` (or `cast_category/2`) with the extension's
current value `data[E.key()] || %{}`, and merges the result under
`data[E.key()]`. Stops at the first extension that returns an error.

Same resilience contract as `columns/1`: a cast that raises, throws,
exits, or returns neither `{:ok, _}` nor `{:error, _}` is logged and
that extension's namespace keeps its current value — one broken
extension must not crash the form on every keystroke (`absorb/3` runs
on `validate`).

# `all`

```elixir
@spec all() :: [module()]
```

All enabled extension modules contributed by registered `PhoenixKit`
modules, in registration order, deduplicated. An extension whose
`key/0` is not a string, is empty, is underscore-prefixed, looks like
a language code, or names a catalogue-owned `data` key is dropped
(logged once per process) — see `@reserved_keys`.

# `columns`

```elixir
@spec columns(:detail_items | :detail_categories) :: [
  PhoenixKitCatalogue.Extension.column()
]
```

Enabled extensions' contributed columns for the catalogue admin's
configurable `:detail_items` / `:detail_categories` tables (spec §2
principle 8, §4 row C4's column follow-up — the "Columns" modal on
`CatalogueDetailLive`).

Each contributed id is namespaced under its extension's `key/0`
(`"<key>:<id>"`) so it can never collide with
`PhoenixKitCatalogue.Web.TableConfig`'s own ids or another
extension's — enforced by rejecting a `key/0` or column `id` that
itself carries the `":"` delimiter (see
`valid_column?/1`), so `key <> ":" <>
id` can never mean two different things.

Same resilience contract as `sections/1` and `absorb/3`: a missing,
disabled, or raising extension — or one whose
`item_columns/0`/`category_columns/0` returns a malformed entry —
contributes nothing rather than breaking the page. That contract also
covers what happens once a column is actually IN USE: a `label` or
`render` that raises, throws, exits, or returns a value
`Phoenix.HTML.Safe` can't turn into HTML degrades to a blank
label/empty cell for that one column instead of taking down the whole
page — see `guarded_label/3` and `guarded_render/3`.

# `duplicate_data`

```elixir
@spec duplicate_data(:item | :category, map()) :: map()
```

A copied item's or category's `data`, with each extension's namespace
passed through that extension's optional `duplicate_data/2`: the
returned map replaces the namespace, `nil` drops it. Namespaces with no
such callback are copied as they are.

Disabled extensions are asked too — their data is still in the row, and
switching a module off must not let a copy carry its external ids. A
callback that raises, or returns something else, drops its namespace
(logged): a copy without the shop's fields is recoverable, a second
row claiming the same external product is not.

# `owned_keys`

```elixir
@spec owned_keys() :: [String.t()]
```

The `data` keys owned by registered extensions, enabled or not — a copy
must not mistake one for a language code.

# `sections`

```elixir
@spec sections(:item | :category) :: [module()]
```

Enabled extension modules that render a section for `kind` (`:item` or
`:category`) — i.e. export `item_section/1` (or `category_section/1`).

---

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