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

Pure templates + name conversions behind `mix mob.new_plugin`.

Inputs are a snake_case plugin name (e.g. `"mob_demo_widget"`) and a tier
(0–4). Output is a list of `{relative_path, content_string}` pairs the Mix
task writes to disk. All conversions live here so the task stays thin and
the templates are unit-testable without filesystem I/O.

Templates mirror the on-device-verified prototypes (`mob_palette_demo` t0,
`mob_demo_haptic_extras` t1, `mob_demo_signature_pad` t2,
`mob_demo_kv_browser` t3, `mob_demo_subapp` t4) so a freshly scaffolded plugin
compiles + activates by the same path the prototypes already prove.

# `file`

```elixir
@type file() :: {Path.t(), String.t()}
```

# `tier`

```elixir
@type tier() :: 0 | 1 | 2 | 3 | 4
```

# `detect_mob_requirement`

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

Resolves the mob version requirement for a freshly scaffolded plugin.

Prefers the version of `:mob` actually resolved in the current project (so a
plugin scaffolded inside a mob 0.7.x app pins `"~> 0.7"`), falling back to
the compiled `@fallback_mob_requirement` when mob isn't loadable (scaffolding
standalone). Impure — the Mix task calls this and threads the result into
`files_for/3`; the templates themselves stay pure.

# `files_for`

```elixir
@spec files_for(tier(), String.t(), String.t()) :: [file()]
```

Returns the file list for a given tier + name. Each entry is
`{relative_path, content}`. `relative_path` is relative to the plugin's
root directory.

`mob_req` is the `mob` version requirement to embed in the generated
`mix.exs` and manifest; defaults to `@fallback_mob_requirement`. The Mix
task passes `detect_mob_requirement/0` so a scaffolded plugin pins the mob
it's being generated against.

# `mob_requirement`

```elixir
@spec mob_requirement(String.t() | Version.t() | nil) :: String.t()
```

Builds a `"~> MAJOR.MINOR"` mob version requirement from a concrete version.

`nil` (mob not detectable) yields the compiled `@fallback_mob_requirement`.
Pure so the derivation is unit-testable independent of what's installed.

# `module_name`

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

Converts `"mob_demo_widget"` → `"MobDemoWidget"`.

# `validate_name`

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

Validates a plugin name (must be a snake_case atom-friendly identifier).

# `validate_tier`

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

Validates a tier (0 through 4).

---

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