PhoenixKit.Notifications.Types (phoenix_kit v1.7.222)

Copy Markdown View Source

Registry of notification types (and optional sub-types) for the per-user preferences UI.

A type is a named group of related activity actions a user can toggle as one unit ("account", "posts", "comments", …). Core types ship with PhoenixKit; external modules contribute more via the optional notification_types/0 callback on PhoenixKit.Module.

A type may declare sub-types for finer control — e.g. "comments" with "replies" and "reactions". Sub-type preference keys are the base key and the sub key joined by a dot ("comments.replies"), mirroring the permission system's dotted sub-keys.

Master-switch semantics (NOT the permission cascade)

The base type is a master switch evaluated at resolution time only:

  • base OFF ⇒ every sub-type under it is muted, regardless of the sub's own value;
  • base ON ⇒ each sub-type follows its own toggle (defaulting to the sub's default).

This is deliberately the OPPOSITE of PhoenixKit.Users.Permissions, where a sub implies its base and grants cascade/normalize the stored set. Here nothing cascades and nothing is normalized in storage — a user's per-sub choices are preserved when the master is toggled off, so flipping it back on restores them. See PhoenixKit.Notifications.Prefs for the resolution.

Shape

%{
  key: "comments",
  label: "Comments",
  description: "Replies and reactions to your comments",
  actions: [],                       # base-owned actions (often [] when fully split)
  default: true,
  sub_types: [
    %{key: "comments.replies",   label: "Replies",   actions: ["comment.replied"], default: true},
    %{key: "comments.reactions", label: "Reactions", actions: ["comment.liked", "comment.disliked"], default: true}
  ]
}

Module authors declare sub keys bare ("replies"); normalize/1 composes them to "<type>.<sub>". Declared keys must not contain . and nesting is one level only — offending entries are dropped with a warning. Unknown top-level map fields are preserved (a seam for future per-channel / digest metadata). Sub-type labels/descriptions are runtime data — mix gettext.extract won't see them (same caveat as permission labels).

Summary

Functions

Every valid preference key — each base key followed by its sub keys, de-duped, stable order. Used to sanitize saves and to build the pause-all set.

The base type keys only (no sub keys) — the master switches.

Default-enabled flag for a base OR dotted key.

Look up a base type by its key. Returns nil when not registered (base keys only).

Resolves an action to the most-specific preference key that owns it — a sub-type's dotted key when a sub-type claims it, otherwise the base type key, otherwise nil.

Full list of types — core plus module-contributed, normalized, stable order.

Base type key for a dotted sub key ("comments.replies""comments").

Resolves an action to the key of its owning base type (back-compat).

Types

sub_type()

@type sub_type() :: %{
  key: String.t(),
  label: String.t(),
  description: String.t(),
  actions: [String.t()],
  default: boolean()
}

t()

@type t() :: %{
  key: String.t(),
  label: String.t(),
  description: String.t(),
  actions: [String.t()],
  default: boolean(),
  sub_types: [sub_type()]
}

Functions

all_pref_keys()

@spec all_pref_keys() :: [String.t()]

Every valid preference key — each base key followed by its sub keys, de-duped, stable order. Used to sanitize saves and to build the pause-all set.

base_keys()

@spec base_keys() :: [String.t()]

The base type keys only (no sub keys) — the master switches.

default_for(key)

@spec default_for(String.t()) :: boolean()

Default-enabled flag for a base OR dotted key.

TOTAL by contract: an unknown key returns true (the fail-open backstop — a false/nil here would let master AND sub go silently falsy).

find(key)

@spec find(String.t()) :: t() | nil

Look up a base type by its key. Returns nil when not registered (base keys only).

key_for_action(action)

@spec key_for_action(String.t()) :: String.t() | nil

Resolves an action to the most-specific preference key that owns it — a sub-type's dotted key when a sub-type claims it, otherwise the base type key, otherwise nil.

Backed by a deterministic action→key index (action_index/0): sub-types are indexed before their base (so a sub wins over its base), and the FIRST claim of an action wins globally (a later duplicate claim is ignored with a warning). nil for unclaimed actions preserves the caller's fail-open behaviour.

list()

@spec list() :: [t()]

Full list of types — core plus module-contributed, normalized, stable order.

parent_type_key(key)

@spec parent_type_key(String.t()) :: String.t() | nil

Base type key for a dotted sub key ("comments.replies""comments").

Returns nil for a base key or an unkeyed value. Safe because declared keys are .-free and nesting is one level (enforced in normalize/1).

type_for_action(action)

@spec type_for_action(String.t()) :: String.t() | nil

Resolves an action to the key of its owning base type (back-compat).

Used by Prefs.user_wants_type?/2. Returns the base key even when a sub-type owns the action.