# `MobDev.Plugin.RuntimeManifest`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/plugin/runtime_manifest.ex#L1)

Builds the host's **runtime plugin manifest** — the device-readable record of
the activated plugins' tier-3/4 contributions.

Tiers 3 and 4 are pure-Elixir and runtime-wired: the host needs to know, on
device, which screens exist, what lifecycle/settings/notification declarations
each plugin made — but `MobDev.Plugin.activated/0` is compile-time only. So at
build time this module gathers those sections (running any spec-v2
`:screens_generator` under the host-config audit) and emits a terms file
(`priv/generated/mob_plugins.exs`) that the core `Mob.Plugins` module reads at
boot. It mirrors the `driver_tab` / `MobPluginBootstrap` codegen pattern, but
for serializable Elixir data instead of native symbols.

Only the behavioral data lives here. Migration files and font/image assets are
physically copied into the host at build time (see `native_build`), not carried
in this manifest.

Everything emitted must be serializable terms — atoms, tuples, maps, strings,
integers. The manifest validator forbids closures in plugin sections so this
always holds.

# `build`

```elixir
@spec build([{Path.t(), map() | nil}]) :: map()
```

Builds the merged runtime manifest map from `{plugin_dir, manifest}` pairs.

Static `:screens` and spec-v2 `:screens_generator` output are combined (each
tagged with its plugin); generators run under `MobDev.Plugin.with_host_config_audit/3`
so an undeclared host-config read fails the build.

# `render`

```elixir
@spec render(map()) :: String.t()
```

Renders the manifest map to the contents of `priv/generated/mob_plugins.exs` —
a self-describing `.exs` that evaluates back to the map.

Every map, nested ones included, is printed with its keys sorted, so the same
manifest renders to the same bytes in any VM. Without that, a map's printed key
order follows atom-creation order (OTP 26+), which differs between runs, and
`mix mob.regen_plugin_manifest --check` reported drift right after a regen
(MOB-383).

# `validate_generated_screens`

```elixir
@spec validate_generated_screens(term(), atom() | nil) :: [map()]
```

Validates a `:screens_generator`'s output, raising on malformed specs.

A generator must return a list of `%{module: atom, default_route: binary}`
maps (a bare map is wrapped). This mirrors `MobDev.Plugin.Manifest`'s static
`:screens` validation so generator-produced screens are held to the same
shape contract — otherwise a typo'd or wrong-typed spec would be written into
`mob_plugins.exs` and silently dropped at boot (`Mob.Plugins.register_screens/0`
pattern-matches `%{module:, default_route:}` and skips non-matching maps),
making the plugin's screen vanish with no build-time error.

Returns the validated screen list (without the `:plugin` tag, which the caller
adds). Public for testing.

# `write`

```elixir
@spec write(Path.t(), map()) :: Path.t()
```

Writes the rendered manifest to `<host_root>/priv/generated/mob_plugins.exs`
and returns the path.

---

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