# `Mobus.Stepwise.ProjectionHelpers`
[🔗](https://github.com/fosferon/mobus_stepwise/blob/main/lib/mobus/stepwise/projection_helpers.ex#L1)

Shared projection helper functions used by all profile projections
(stepwise, FSM, flow).

Extracted to eliminate duplication across projection components.

# `build_extensions`

```elixir
@spec build_extensions(map(), map()) :: map()
```

Builds the projection extensions map.

When `spec.projection_enricher` names a module exporting `enrich/2`,
it is called with the base extensions and the runtime, and its return
replaces the extensions map. When absent, extensions contain just the
runtime meta (if any).

This is shared so all profiles (stepwise, FSM, flow) can populate
extensions consistently.

# `existing_atom`

```elixir
@spec existing_atom(String.t()) :: atom() | nil
```

Safe conversion of a binary key to an existing atom. Returns `nil`
if the atom doesn't exist in the atom table, rather than raising.

# `interpolate_topic`

```elixir
@spec interpolate_topic(String.t(), map()) :: String.t() | nil
```

Replaces `{key}` placeholders in a topic template with values from
the runtime context. Returns `nil` if any placeholder is missing or empty.

# `subscriptions_for`

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

Builds the subscriptions list from spec declarations and runtime context,
prepending the mandatory workflow_execution topic and interpolating
`{placeholder}` variables from runtime context.

# `ui_for`

```elixir
@spec ui_for(map(), atom() | String.t(), map()) :: map() | nil
```

Resolves a UI descriptor for a given state from the spec definition.

Looks up `state_def.ui` with optional `key` and `assigns` and merges
in context assigns (`context` and `state`). Returns `nil` if no UI key
is configured for the state.

---

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