# `PhoenixKitCatalogue.Web.Settings`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.44.2/lib/phoenix_kit_catalogue/web/settings.ex#L1)

Read/write helpers for the catalogue's operational settings — the
AI-translation sweep the `PhoenixKitCatalogue.Workers.TranslationSweepWorker`
reads on every tick, and the admin-list preferences the web layer reads at
render time.

`PhoenixKitCatalogue.Web.SettingsLive` (Settings → Catalogue) is the page
that writes them. It arrived late: the sweep keys shipped with no UI at all,
so turning the sweep on was an operator call into `update_*` and nobody
could see it was off (boss via Max, 2026-09-21).

Kept as a thin module rather than folding the keys into
`PhoenixKitCatalogue` itself: the worker and the templates only ever read,
the settings page only ever writes, and neither needs the other's concerns.

| key                                            | type | default        |
|-------------------------------------------------|------|----------------|
| `catalogue_row_context_menu_enabled`             | bool | `true`         |
| `catalogue_item_seo_fields_visible`              | bool | `false`        |
| `catalogue_translation_sweep_enabled`            | bool | `false`        |
| `catalogue_translation_sweep_interval_minutes`   | int  | `60`           |
| `catalogue_translation_sweep_langs`              | json | see below      |
| `catalogue_translation_sweep_max_per_run`        | int  | `200`          |

`catalogue_translation_sweep_langs` defaults to every enabled language
except the primary one — computed at read time (not stored) so a language
toggled on/off in the languages module is picked up without a settings
write. Stored as `%{"codes" => [...]}` rather than a bare JSON array:
`PhoenixKit.Settings`'s `value_json` column is an Ecto `:map`, which
rejects a top-level list.

# `context_menu_enabled?`

```elixir
@spec context_menu_enabled?() :: boolean()
```

Does a right-click on an admin list row open that row's menu at the pointer?

Default `true` — the gesture is what a desktop user expects of a list, and
a row that offers nothing on right-click reads as unfinished. Off leaves
the browser's own menu in place everywhere (Copy, Inspect, Open in new
tab), which is the reason to want it off.

Read at render time by the row components, which simply omit the
`data-row-menu-context` attribute when it is false — so turning it off
removes the wiring rather than disabling it in the browser.

# `seo_fields_visible?`

```elixir
@spec seo_fields_visible?() :: boolean()
```

Does the item form show its URL slug, SEO title and SEO description?

Default `false`: the current client has no use for them and they crowd
the form (boss via Max, 2026-09-21: "hidden for now"). Hidden is not
gone — the form still carries their stored values, so a save keeps them,
and turning this on brings the fields back with their contents.

# `sweep_enabled?`

```elixir
@spec sweep_enabled?() :: boolean()
```

Is the automatic sweep enabled?

# `sweep_interval_minutes`

```elixir
@spec sweep_interval_minutes() :: pos_integer()
```

Minutes between sweep ticks.

# `sweep_langs`

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

Target languages the sweep considers. Defaults to every enabled language
except the primary one when nothing is stored; a stored list is
intersected with the enabled languages, so a language disabled after
the setting was written stops receiving sweep jobs — the same check the
Translations page applies to a manual Translate.

# `sweep_max_per_run`

```elixir
@spec sweep_max_per_run() :: pos_integer()
```

Maximum number of translation jobs one sweep tick enqueues.

# `update_context_menu_enabled`

```elixir
@spec update_context_menu_enabled(boolean()) :: {:ok, struct()} | {:error, term()}
```

Turns the right-click row menu on or off.

# `update_seo_fields_visible`

```elixir
@spec update_seo_fields_visible(boolean()) :: {:ok, struct()} | {:error, term()}
```

Shows or hides the item form's slug and SEO fields.

# `update_sweep_enabled`

```elixir
@spec update_sweep_enabled(boolean()) :: {:ok, struct()} | {:error, term()}
```

Toggles the automatic sweep. Flipping it ON seeds the sweep's
self-rescheduling chain (`TranslationSweepWorker.ensure_scheduled/0`) —
the boot-time bootstrap only seeds it when already enabled (additive
for hosts that never opt in), so this write is what starts the chain
the first time an operator turns the sweep on.

# `update_sweep_interval_minutes`

```elixir
@spec update_sweep_interval_minutes(pos_integer()) ::
  {:ok, struct()} | {:error, term()}
```

Sets the sweep interval, in minutes.

# `update_sweep_langs`

```elixir
@spec update_sweep_langs([String.t()]) :: {:ok, struct()} | {:error, term()}
```

Sets the sweep's target languages.

# `update_sweep_max_per_run`

```elixir
@spec update_sweep_max_per_run(pos_integer()) :: {:ok, struct()} | {:error, term()}
```

Sets the per-tick enqueue cap.

---

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