# `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).

# `android_font_resource_name`

```elixir
@spec android_font_resource_name(String.t()) :: String.t()
```

The Android `res/font/` resource name for a font file: lowercase, the
extension dropped, and any character outside `[a-z0-9_]` replaced with `_`
(Android resource-name rules). `"Georgia.ttf"` → `"georgia"`,
`"Inter-Regular.ttf"` → `"inter_regular"`. The renderer normalises the `font:`
prop the same way to find the resource. A leading non-letter is prefixed with
`f_` so the name is a valid resource identifier.

# `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
`<android_font_resource_name>.<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.

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*
