# `MishkaGervaz.Form.Web.State.Helpers`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.1-alpha.3/lib/mishka_gervaz/form/web/state.ex#L239)

Shared helpers for `MishkaGervaz.Form.Web.State`.

Two reasons these live outside the `__using__` macro:

1. **Reuse across the macro and user overrides.** A user module that
   overrides `init/3` (via `use MishkaGervaz.Form.Web.State`) can
   call any helper here without redefining it. The macro itself
   imports them as `StateHelpers`.
2. **Smaller compiled bytecode per consumer.** Helpers compile once
   in this module rather than being re-emitted into every macro
   expansion.

Two functional groups:

- **Config getters** (`get_layout_mode/1`, `get_uploads/1`,
  `get_submit/1`, …) — pull a typed value out of the runtime
  `Info.config(resource)` map with sensible defaults.
- **Step helpers** (`groups_for_step/3`) and **access**
  (`mode_allowed?/3`, `resolve_access/1`) — the bits of logic the
  macro and external callers (e.g. `Form.Web.Live`) both need.

## `mode_allowed?/3` — `:restricted` semantics

The `:restricted` field on a `source` map (or a per-mode entry in
`:access_rules`) accepts two shapes with **deliberately different
contracts**:

- `restricted: true` — applies the master gate. Mode is allowed iff
  `state.master_user?`. Use this for the standard "admin-only"
  pattern.
- `restricted: fn state -> boolean end` — function is the **final
  word**. The master gate is **not** layered on top. Returning
  `true` means "this user is restricted"; the mode is denied.
  Returning `false` allows the mode unconditionally.

The asymmetry is intentional: the boolean form is the common case
where you just want master-only; the function form is the escape
hatch for callers that need the full state (role, dirty?, current
step, etc.) to decide and don't want master-gate sugar layered on.
Reach for the boolean unless you specifically need to bypass it.

See `MishkaGervaz.Form.Web.State`,
`MishkaGervaz.Form.Web.State.Access.Default`, and
`MishkaGervaz.Form.Web.Live`.

# `generate_stream_name`

```elixir
@spec generate_stream_name(module()) :: atom()
```

# `get_footer`

```elixir
@spec get_footer(map()) :: map() | nil
```

# `get_header`

```elixir
@spec get_header(map()) :: map() | nil
```

# `get_hooks`

```elixir
@spec get_hooks(map()) :: map()
```

# `get_layout_columns`

```elixir
@spec get_layout_columns(map()) :: 1 | 2 | 3 | 4
```

# `get_layout_mode`

```elixir
@spec get_layout_mode(map()) :: :standard | :wizard | :tabs
```

# `get_layout_navigation`

```elixir
@spec get_layout_navigation(map()) :: :sequential | :free
```

# `get_notices`

```elixir
@spec get_notices(map()) :: [map()]
```

# `get_submit`

```elixir
@spec get_submit(map()) :: map()
```

# `get_uploads`

```elixir
@spec get_uploads(map()) :: [map()]
```

# `groups_for_step`

```elixir
@spec groups_for_step([map()], [map()], atom()) :: [map()]
```

# `mode_allowed?`

```elixir
@spec mode_allowed?(map() | nil, atom(), map()) :: boolean()
```

# `resolve_access`

```elixir
@spec resolve_access(module()) :: module()
```

---

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