# `MobDev.Adopt.Patcher`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/adopt/patcher.ex#L1)

Pure helpers for the `mix mob.adopt` Elixir-source patches and
content generators: the LiveView bridge patches (`assets/js/app.js`
MobHook + `root.html.heex` bridge div), the `mix.exs` dep injection,
and the generated `mob_screen.ex` / `mob_app.ex` / `mob.exs` /
`src/<app>.erl` contents.

Duplicated from `MobNew.LiveViewPatcher` (mob_new, the project
generator archive). Only the transitive closure `mix mob.adopt`
actually exercises was copied — `mob.new`'s notes-app generators
(`repo_content`, `note_content`, the LiveView starter screens, …)
stay in mob_new. Phase 5 of `build_system_migration.md` reunifies the
two copies behind a single Igniter-based path; until then both repos
carry their own copy (mob_new can't depend on mob_dev — it's a
self-contained Mix archive; see `ArchiveSelfContainedTest`).

## Compile-time regex

Like the mob_new original, this module compiles regexes at runtime via
`Regex.compile!/1` rather than `~r//` literals — the `~r//` form bakes
a bytecode pattern that calls `:re.import/1`, removed in OTP 28.0 (the
version Mob's bundled iOS/Android tarballs ship). See mob `AGENTS.md`
rule #9.

# `erlang_entry_content`

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

Generates the Erlang bootstrap for a LiveView project.

Calls `ModuleName.MobApp.start()` instead of `ModuleName.App.start()`.

# `inject_deps`

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

Injects mob / mob_dev dependencies into the `deps/0` function in `mix.exs` content.

`mob_dep` and `mob_dev_dep` are dependency tuple strings (already formatted —
e.g. `~s({:mob, "~> 0.5"})` or `~s({:mob, path: "/path"})`). They are parsed
back to AST and inserted at the end of the user's deps list.

Idempotent: no-op if `:mob` is already declared in the user's deps list,
regardless of indentation or trailing-comma shape.

## Implementation note

Deps are injected by an AST walk (robust against Phoenix-version / formatter
variation), then serialized with **stdlib only** (`Macro.to_string` +
`Code.format_string!`). The mob_new original is reachable from `mix mob.new`
running as a Mix *archive*, and archives don't bundle runtime deps — so it
must not call any non-stdlib module (Sourceror). Preserved here verbatim.

# `inject_ecto_sqlite3`

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

Adds `{:ecto_sqlite3, "~> 0.18"}` to the deps list in `mix.exs` content
if not already present. The generated `mob_app.ex` (LiveView flavour)
calls `Application.ensure_all_started(:ecto_sqlite3)` and runs
`Ecto.Migrator` on-device, so the dep is required whenever that
template is emitted.

Idempotent — no-op when `ecto_sqlite3` is already in the deps string.

# `inject_mob_bridge_element`

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

Injects the hidden bridge `<div>` immediately after the opening `<body>` tag.

Idempotent: returns unchanged content if mob-bridge is already present.

# `inject_mob_hook`

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

Injects the MobHook definition and registration into the given `app.js` content.

Idempotent: returns unchanged content if MobHook is already present.

# `mob_app_content_thin`

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

Generates a thin-client `<App>.MobApp` for projects where the BEAM on
device does NOT host Phoenix/Hologram/game state — instead the WebView
points at a deployed Phoenix server and the device's BEAM is just the
native interop layer.

Produced when `mix mob.adopt --no-live-view` is invoked. The thin
variant uses `use Mob.App` with `navigation/1` + `on_start/0`
callbacks (the same shape `mix mob.new` generates for native mode),
rather than the LV-flavored `def start do ... end` that boots the
host Phoenix endpoint on-device.

# `mob_bridge_element`

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

Returns the hidden bridge element string (for test assertions).

# `mob_exs_content`

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

Generates mob.exs config content for a LiveView project.

# `mob_hook_js`

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

Returns the MobHook JS string (for tests and warning messages).

# `mob_live_app_content`

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

Generates the `mob_app.ex` entry point for a LiveView project.

This module is called from the Erlang bootstrap (`src/app_name.erl`) instead
of a native `Mob.App` module. It starts the Phoenix OTP application (which
boots the endpoint) and then starts `MobScreen` to open the WebView.

Unlike native Mob apps, this does NOT `use Mob.App` — Phoenix owns the
supervision tree. Mob is wired in at the BEAM entry level only.

`secret_key_base` and `signing_salt` are embedded directly because Mix config
files (`config/*.exs`) are not loaded on-device — `Application.put_env/3` is
the only way to configure the endpoint before `ensure_all_started/1` runs.
The on-device port defaults to a per-app hash (4200..4999) — see
`default_liveview_port/0` for the collision rationale.

# `mob_screen_content_install`

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

Generates `MobScreen` content for `mix mob.adopt`.

The generated module reads the WebView URL from application config:

    config :mob, host_url: "https://your-app.example.com/"

Default if unset is `http://127.0.0.1:4000/`, suitable for on-device
BEAM hitting a local Phoenix endpoint. `mix mob.adopt --host-url
<URL>` writes the config entry so the user doesn't need to edit
`config/config.exs` by hand.

---

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