PhoenixKitProjects.Extensions.Extension (PhoenixKitProjects v0.21.1)

Copy Markdown View Source

A project extension type — the catalog entry describing a capability that can be enabled per project (the hub's equivalent of a site module).

The provider contract (decoupled by design)

A provider module exposes extensions by defining a zero-arity phoenix_kit_project_extensions/0 returning a list of plain maps — it does NOT depend on phoenix_kit_projects; this module normalizes the maps via from_map/2 (mirrors phoenix_kit_dashboards' widget contract):

# in phoenix_kit_crm.ex
def phoenix_kit_project_extensions do
  [
    %{
      key: "crm_client",
      name: "Client",
      description: "Attach a CRM company + contacts to this project",
      icon: "hero-building-office-2",
      module_key: "crm",
      tabs: [
        %{key: "client", label: "Client",
          lv: PhoenixKitCRM.Web.ProjectClientLive}
      ],
      config_schema: [
        %{key: "company_uuid", type: :string, label: "Company"}
      ]
    }
  ]
end

Contributed tab LVs are rendered by the hub via live_render with the projects embed-session contract (project_uuid / current_user_uuid / locale / emit keys) — the LV must be mountable off-router (:not_mounted_at_router, no handle_params/3), exactly like this module's own embeddable LVs.

Forward-looking surface (declared now, dispatched incrementally)

Per the 2026-08-05 plan-review panel: the contract carries its FULL surface from day one so providers never need rework when later hub layers land —

  • feature_flags — per-project flags this extension owns (%{key, label, default, requires: [...]}); the hub's Features layer resolves them.
  • permission_actions — action keys the extension's UI asks PhoenixKitProjects.Authz.can?/5 about.
  • notification_types — passthrough entries merged into this module's notification_types/0 (core's prefs-UI shape).
  • on_enable / on_disable{module, function} called with (project_uuid, config) after a successful toggle. Best-effort: failures are logged, never abort the toggle.
  • data_retention:keep (only supported value; documented Redmine semantics: disabling hides, never deletes; a future :purge_api value may name an explicit cleanup entry point).

Instances

Enablement rows are instance-keyed (instance_key, default "default"); v1 exposes toggle semantics (one instance per extension per project) but the storage and lookups thread the instance key throughout, so Basecamp-style multi-instance tools are a constraint relaxation, not a migration.

Summary

Functions

Normalizes a provider's plain map into {:ok, %Extension{}} or {:error, reason}. Accepts atom or string keys. Invalid tabs/callbacks are dropped with a warning rather than failing the whole extension.

Types

t()

@type t() :: %PhoenixKitProjects.Extensions.Extension{
  category: String.t() | nil,
  config_schema: [map()],
  data_retention: :keep,
  default_enabled: boolean(),
  description: String.t() | nil,
  feature_flags: [map()],
  icon: String.t(),
  key: String.t(),
  module_key: String.t() | nil,
  name: String.t(),
  notification_types: [map()],
  on_disable: {module(), atom()} | nil,
  on_enable: {module(), atom()} | nil,
  permission: String.t() | nil,
  permission_actions: [atom() | String.t()],
  source: module() | nil,
  tabs: [tab()]
}

tab()

@type tab() :: %{
  key: String.t(),
  label: String.t(),
  icon: String.t() | nil,
  lv: module()
}

Functions

from_map(map, source)

@spec from_map(map(), module()) :: {:ok, t()} | {:error, term()}

Normalizes a provider's plain map into {:ok, %Extension{}} or {:error, reason}. Accepts atom or string keys. Invalid tabs/callbacks are dropped with a warning rather than failing the whole extension.