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

Pure planners for the tier-3 build-time file merges — migrations, fonts, and
images — that `native_build` copies into the host app at build.

Unlike the runtime manifest (behavioral data read on device), these are
physical files: a plugin's migration `.exs`, font, and image files are
meaningless as build-machine paths on device, so they're copied into the host
bundle at build time. This module computes *what* gets copied where (pure +
unit-tested); `native_build` does the I/O (listing dirs, copying, patching
Info.plist).

# `image_bundle_path`

```elixir
@spec image_bundle_path(atom() | String.t(), String.t()) :: String.t()
```

The on-device bundle path a `plugin://<plugin>/<file>` reference resolves to.
Plugin images are copied here at build time; the core `plugin://` resolver
maps to the same convention. Returns a path relative to the app bundle root.

# `merge_ui_app_fonts`

```elixir
@spec merge_ui_app_fonts(String.t(), [String.t()]) :: String.t()
```

Merges font basenames into an iOS `Info.plist` XML string under `UIAppFonts`,
creating the array if absent and de-duplicating existing entries. Pure string
transform over the plist's XML (the same approach as the plist-keys merge).

# `migration_copies`

```elixir
@spec migration_copies([%{repo_namespace: String.t(), files: [Path.t()]}], Path.t()) ::
  [
    {Path.t(), Path.t()}
  ]
```

Plans the migration copies: maps each plugin migration source file to a
destination under the host's `migrations_dir`, prefixed with the plugin's
`repo_namespace` so files from different vendors don't collide.

Takes `[%{repo_namespace, files: [src_path]}]` (the caller lists each plugin's
migration dir) and the host migrations dir; returns `[{src, dest}]`.

# `namespaced_filename`

```elixir
@spec namespaced_filename(String.t(), String.t()) :: String.t()
```

Namespaces a migration filename with the plugin's `repo_namespace`, inserting
it into the *name* part after the `<version>_` prefix so Ecto can still parse
the leading-integer version (`20260101000000_create.exs` →
`20260101000000_kv_create.exs`). Files without a numeric version prefix fall
back to a plain prefix.

The namespace is **always** inserted — there is no "already namespaced?" guard.
Migration sources are always the plugin author's raw files (never our output),
so re-run idempotency comes from the deterministic source→dest mapping, not
from inspecting the name. A guard that skipped namespacing when the description
happened to begin with the namespace text would silently drop the namespace and
cause cross-vendor collisions, so we don't do it.

# `parse_ui_app_fonts`

```elixir
@spec parse_ui_app_fonts(String.t()) :: [String.t()]
```

Extracts the current `UIAppFonts` entries from an `Info.plist` (or `[]`).

# `plan_android_font_copies`

```elixir
@spec plan_android_font_copies([Path.t()]) ::
  {:ok, [{Path.t(), String.t()}]}
  | {:error, {:font_resource_collision, String.t(), [Path.t()]}}
```

Plans the Android `res/font/` copies: each distinct source maps to
`<Mob.Font.android_resource_name/1>.<ext>`. Two distinct sources
normalising to the same resource name (e.g. `Inter-Regular.ttf` and
`Inter_Regular.ttf` both → `inter_regular.ttf`) would silently overwrite
each other, so that is surfaced as an error.

Uses `Mob.Font.android_resource_name/1` — the SAME function
`Mob.Theme.font/2` uses to compute a theme font token's Android name —
so the resource this planner copies to and the name a theme token
points at can't drift apart. See `MOB_FONTS.md` in the `mob` repo.

Returns `{:ok, [{src, res_filename}]}` or
`{:error, {:font_resource_collision, res_filename, [src, ...]}}`.

# `plan_ios_font_bundle`

```elixir
@spec plan_ios_font_bundle([Path.t()]) ::
  {:ok, [{Path.t(), String.t()}]}
  | {:error, {:font_basename_collision, String.t(), [Path.t()]}}
```

Plans the iOS font-bundle copies: each distinct source font copies to the `.app`
root under its basename (and is listed in `UIAppFonts` by basename). Two distinct
sources sharing a basename (e.g. two plugins both shipping `Icons.ttf`) would
silently overwrite each other in the bundle and collapse to a single `UIAppFonts`
entry, so that is surfaced as an error instead of a silent loss.

Returns `{:ok, [{src, dest_basename}]}` or
`{:error, {:font_basename_collision, basename, [src, ...]}}`.

---

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