Discovers and caches the project-extension catalog.
The catalog is the union of every PhoenixKit module defining
phoenix_kit_project_extensions/0 (see
PhoenixKitProjects.Extensions.Extension for the contract). This module's
own PhoenixKitProjects is one such provider — the built-in Tasks
extension and the hub-side bridge descriptors (for modules that don't
self-declare, e.g. comments) ship through the same entry point, no
special-cased path.
Host apps contribute providers without being PhoenixKit modules:
config :phoenix_kit_projects, extension_providers: [MyApp.ProjectExtensions]The catalog structure is memoized in :persistent_term (the dashboards
Registry pattern). Module enablement and permission are re-checked
live on every read; a newly installed provider or changed descriptor needs
refresh/0 (this module calls it on its own enable).
Summary
Functions
Whether the extension's backing site module (if any) is enabled.
The full extension catalog, keyed by extension key. Memoized.
Category keys with display labels, in scan order.
The category an extension belongs to: its declared category when it
names a known one, else the built-in fallback for its key, else "more".
Look up one extension type by key.
Extensions grouped for display: [{category_key, label, [extension]}],
in scan order, with empty categories dropped.
All catalog extensions, stable order (built-ins first, then by name).
Catalog extensions OFFERABLE right now: the backing site module (if any)
is enabled. This is the availability half of the gate; per-scope
permission is checked separately via visible_for_scope?/2 because the
Modules panel wants to show unavailable extensions greyed-out rather than
hidden.
Rebuild the catalog from every discovered provider and re-cache it.
Whether a scope may SEE this extension's contributed surface. The
required permission resolves permission → module_key → the hub's
own "projects" — so a tab re-exporting a SIBLING module's data
(CRM/entities/publishing) requires that module's permission, exactly
what the descriptor comments promise, while hub-owned built-ins ride
the projects permission the viewer already holds (final panel, Grok:
the gate existed but nothing consulted module_key, so any projects
viewer could read linked sibling data through a tab). Fail-closed on
a nil scope.
Functions
@spec available?(PhoenixKitProjects.Extensions.Extension.t()) :: boolean()
Whether the extension's backing site module (if any) is enabled.
@spec catalog() :: %{ required(String.t()) => PhoenixKitProjects.Extensions.Extension.t() }
The full extension catalog, keyed by extension key. Memoized.
Category keys with display labels, in scan order.
@spec category_of(PhoenixKitProjects.Extensions.Extension.t()) :: String.t()
The category an extension belongs to: its declared category when it
names a known one, else the built-in fallback for its key, else "more".
@spec get(String.t()) :: PhoenixKitProjects.Extensions.Extension.t() | nil
Look up one extension type by key.
@spec group_by_category([PhoenixKitProjects.Extensions.Extension.t()]) :: [ {String.t(), String.t(), [PhoenixKitProjects.Extensions.Extension.t()]} ]
Extensions grouped for display: [{category_key, label, [extension]}],
in scan order, with empty categories dropped.
@spec list() :: [PhoenixKitProjects.Extensions.Extension.t()]
All catalog extensions, stable order (built-ins first, then by name).
@spec list_available() :: [PhoenixKitProjects.Extensions.Extension.t()]
Catalog extensions OFFERABLE right now: the backing site module (if any)
is enabled. This is the availability half of the gate; per-scope
permission is checked separately via visible_for_scope?/2 because the
Modules panel wants to show unavailable extensions greyed-out rather than
hidden.
@spec refresh() :: %{ required(String.t()) => PhoenixKitProjects.Extensions.Extension.t() }
Rebuild the catalog from every discovered provider and re-cache it.
@spec visible_for_scope?( PhoenixKitProjects.Extensions.Extension.t(), PhoenixKit.Users.Auth.Scope.t() | nil ) :: boolean()
Whether a scope may SEE this extension's contributed surface. The
required permission resolves permission → module_key → the hub's
own "projects" — so a tab re-exporting a SIBLING module's data
(CRM/entities/publishing) requires that module's permission, exactly
what the descriptor comments promise, while hub-owned built-ins ride
the projects permission the viewer already holds (final panel, Grok:
the gate existed but nothing consulted module_key, so any projects
viewer could read linked sibling data through a tab). Fail-closed on
a nil scope.