# `Mob.Plugins`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/plugins.ex#L1)

On-device access to the activated plugins' tier-3/4 contributions.

Tiers 3 (multi-screen) and 4 (sub-app) are pure-Elixir and runtime-wired: the
host needs to know, while running, which screens / lifecycle hooks / settings /
notification handlers the activated plugins declared. mob_dev bakes that into
the host's `priv/generated/mob_plugins.exs` at build time (see
`mix mob.regen_plugin_manifest`); this module reads it once at boot and feeds
the data to the core wiring (`Mob.App` registers the screens, the lifecycle
dispatcher calls the hooks, the notification dispatch consults the handlers, and the
settings store namespaces by plugin).

When no tier-3/4 plugin is active (or the manifest hasn't been regenerated) the
file is absent and every accessor returns the empty set — tiers 0-2 are
unaffected.

# `apply_default_style`

```elixir
@spec apply_default_style() :: :ok
```

Applies the default style's theme at boot (`Mob.Theme.set/1` with the style
package's theme module). No default → no-op (neutral baseline / the host's
own `use Mob.App, theme:`). A broken theme module logs instead of failing
boot — the app renders baseline rather than not at all.

# `boot`

```elixir
@spec boot(atom() | nil) :: :ok
```

Boot-time entry point: load the host's manifest and register the activated
plugins' screens so they're navigable. Called from `Mob.App.start/0`.

`nil` (no resolvable host app — e.g. on host BEAM / tests) is a no-op. Safe
to call when no tier-3/4 plugin is active: the manifest is empty and nothing
is registered.

# `composites`

```elixir
@spec composites() :: [map()]
```

Activated plugins' pure-Elixir composite components
(`%{atom, expand: {M, F}}`) — the manifest `expand:` ui_components form.

# `default_style`

```elixir
@spec default_style() :: atom() | nil
```

The configured `:default_style` name (or nil — neutral baseline).

# `dispatch_notification`

```elixir
@spec dispatch_notification(map()) :: :handled | :unhandled
```

Routes a notification payload to the first matching plugin handler.

Walks `notification_handlers/0` in order; the first handler whose `:match`
(a map prefix-matched against the payload, or a `{M,F,arity}` predicate) wins,
and its `{M,F,arity}` handler is invoked with the payload. Returns `:handled`
or `:unhandled` (the host's own `handle_info` takes the unhandled case).

This is the pure routing core; the central notification delivery that feeds it
is wired natively (Phase 3).

# `get_setting`

```elixir
@spec get_setting(atom(), atom()) :: term()
```

Reads a plugin setting, falling back to the schema default when unset.

Backed by `Mob.State` (the persistent K/V store) under a per-plugin namespaced
key. Returns `nil` for an unknown plugin/key.

# `install`

```elixir
@spec install(map()) :: :ok
```

Caches an already-built manifest (used by `load/1` and tests).

# `lifecycle`

```elixir
@spec lifecycle() :: [map()]
```

Activated plugins' lifecycle declarations.

# `load`

```elixir
@spec load(atom()) :: map()
```

Reads the host app's generated manifest and caches it in `:persistent_term`.

`otp_app` is the host application name (e.g. `:mob_plugin_demo`); the file is
resolved under its `priv/`. Called once from `Mob.App.start/0`. Returns the
loaded manifest (also retrievable later via `manifest/0`).

# `manifest`

```elixir
@spec manifest() :: map()
```

The cached manifest, or the empty set if nothing has been loaded.

# `nifs`

```elixir
@spec nifs() :: [atom()]
```

Activated plugins' NIF module atoms. `boot/1` loads each at startup so an iOS
plugin NIF's `load` callback fires eagerly (registering any permission handler
it owns) before a screen can request that permission.

# `notification_handlers`

```elixir
@spec notification_handlers() :: [map()]
```

Activated plugins' notification handlers, in dispatch order.

# `put_setting`

```elixir
@spec put_setting(atom(), atom(), term()) :: :ok | {:error, term()}
```

Writes a plugin setting after validating its value against the schema `type`.

Returns `:ok`, `{:error, {:invalid_type, type}}`, or `{:error, :unknown_setting}`.

# `read`

```elixir
@spec read(atom()) :: map()
```

Reads + evaluates the manifest for `otp_app` without caching. Returns the
empty set when the priv dir or file is absent, or the file is malformed (a
missing manifest must never crash boot).

# `read_path`

```elixir
@spec read_path(Path.t()) :: map()
```

Reads + evaluates a manifest from an explicit path (empty set on any failure).

# `register_composites`

```elixir
@spec register_composites() :: :ok
```

Registers each manifest-declared composite expander into `Mob.Composite`.
Malformed entries are skipped (the build validator is where shape errors
surface; a stale hand-edited manifest must not crash boot).

# `register_screens`

```elixir
@spec register_screens() :: :ok
```

Registers each manifest screen into `Mob.Nav.Registry` under an atom derived
from its `default_route`, so the host (or the plugin's own screens) can
navigate to it by route. The module is also directly navigable. The host
still chooses *where* to surface a plugin screen in its `navigation/1`
structure — registration only makes the destination resolvable.

# `resolve_image`

```elixir
@spec resolve_image(String.t()) :: {:ok, String.t()} | :passthrough | :error
```

Resolves a `plugin://<plugin>/<file>` image reference to its on-device bundle
path (`assets/plugin/<plugin>/<file>`), the convention `native_build` copies
plugin images to. The renderer calls this when an image `src` uses the
`plugin://` scheme; a non-plugin URL returns `:passthrough` so normal image
handling continues. Returns `:error` for a malformed `plugin://` reference.

# `screens`

```elixir
@spec screens() :: [map()]
```

Activated plugins' screen declarations (`%{plugin, module, default_route}`).

# `settings`

```elixir
@spec settings() :: [map()]
```

Activated plugins' settings declarations.

# `settings_editor`

```elixir
@spec settings_editor(atom()) :: {:ok, module()} | :error
```

Resolves the screen a host pushes to let users edit a plugin's settings.

A tier-4 plugin owns its settings UX; the host only needs the entry point, so
it calls this to get the module to `push_screen/2`. Returns `:error` when the
plugin declared no `editor_screen`.

# `start_lifecycle`

```elixir
@spec start_lifecycle() :: :ok
```

Starts the tier-4 plugin supervisor (runs each plugin's `lifecycle.on_start`,
starts its `supervised` children, and the lifecycle event dispatcher). Called
from `Mob.App.start/0` after the host's own `on_start/0`. No-op when no plugin
declares a `:lifecycle`.

# `styles`

```elixir
@spec styles() :: [map()]
```

Activated token-only style packages (`%{name, theme}`), from MOB_STYLES.md's lane.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
