Tiny utilities shared by every catalogue LiveView. Imported into LVs
via the standard import PhoenixKitCatalogue.Web.Helpers line.
Currently exports:
actor_opts/1— extract the current user's UUID from socket assigns, return[actor_uuid: uuid]for theopts \\ []keyword list every mutating context function accepts. Returns[]when no user is signed in (e.g. inside a test that mounts the LV with a bare conn). The atom is suitable to thread throughCatalogue.create_*/update_*/trash_*/restore_*/permanently_delete_*etc.actor_uuid/1— the raw UUID (ornil). Use when you need the value directly rather than a keyword list, e.g. when building activity-log metadata in a LiveView.log_operation_error/3— engineer-visibleLogger.errorfor a failed mutation plus an Activity row taggeddb_pending: trueso the user-visible audit feed records the attempted action even when it fails. SeePhoenixKitCatalogue.Catalogue.ActivityLogfor the success-vs-failure layering.
Summary
Types
Convenience alias for the keyword list shape mutating ctx fns accept.
Functions
Extracts [actor_uuid: uuid] from socket.assigns.phoenix_kit_current_user.
Returns the current user's UUID from socket assigns, or nil.
Maps an LV operation string + entity_type to the canonical activity action atom the catalogue context already uses on the success path.
Logs a failed LV mutation in two places at once
Translates a catalogue/category/item/manufacturer/supplier status
field value to a localised label via gettext.
Types
@type actor_opts() :: [{:actor_uuid, Ecto.UUID.t()}] | []
Convenience alias for the keyword list shape mutating ctx fns accept.
Functions
@spec actor_opts(Phoenix.LiveView.Socket.t()) :: actor_opts()
Extracts [actor_uuid: uuid] from socket.assigns.phoenix_kit_current_user.
Returns [] when no user is signed in. Pass the result straight into
any PhoenixKitCatalogue.Catalogue mutating function as its trailing
opts argument.
@spec actor_uuid(Phoenix.LiveView.Socket.t()) :: Ecto.UUID.t() | nil
Returns the current user's UUID from socket assigns, or nil.
Maps an LV operation string + entity_type to the canonical activity action atom the catalogue context already uses on the success path.
Falls back to nil when the operation doesn't follow the
<verb>_<entity> shape; the caller skips the audit-row write in
that case (engineer log still fires).
@spec log_operation_error(Phoenix.LiveView.Socket.t(), String.t(), map()) :: :ok
Logs a failed LV mutation in two places at once:
- Engineer log —
Logger.errorwith the operation, the LV-level entity context, and the changeset / atom reason. This is the rich-context line that production-incident triage reads. - User-visible audit row — an Activity entry with the same
action atom the success path would have written, plus
metadata.db_pending: true. The audit feed therefore records what the user attempted, not just what succeeded — a deliberate change in the post-Apr 2026 pipeline (workspaceAGENTS.mdC12 agent #2 — "Activity logging coverage").
The action atom is derived from operation via
derive_activity_action/2. Validation cycles (form-validate
events) never reach this helper — by construction it's only called
from {:error, _} handle_event branches, where the failure is a
real infrastructure / consistency error worth auditing.
Expected context keys
:entity_type—"item"/"category"/"catalogue"/"manufacturer"/"supplier"(drives both the activityresource_typeand the action-atom prefix).:entity_uuid— primary-key UUID; lands asresource_uuid.:reason— an%Ecto.Changeset{}, an atom, or any otherinspectable shape. Logged engineer-side; on the audit row it's summarised into PII-safemetadata.error_keys(changeset field names only — never values, since user-typed strings can carry PII).
Activity-log failures (missing table, ownership errors, sandbox
exit) are swallowed by ActivityLog.log/1; they never bubble up
to the LV.
Translates a catalogue/category/item/manufacturer/supplier status
field value to a localised label via gettext.
Handles every status string that any catalogue schema can emit
(active / inactive / archived / deleted / discontinued)
with explicit literal gettext(...) clauses so mix gettext.extract
picks them up. Unknown status values pass through unchanged — never
use String.capitalize/1 on translated text because the result
would pin English casing on a value the extractor can't see.