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
Functions
@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.
@spec base_keys() :: [String.t()]
The base type keys only (no sub keys) — the master switches.
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).
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.
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.
@spec list() :: [t()]
Full list of types — core plus module-contributed, normalized, stable order.
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).
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.