# `PhoenixKitManufacturing.Web.MachineTypeTemplateLive`
[🔗](https://github.com/BeamLabEU/phoenix_kit_manufacturing/blob/0.4.1/lib/phoenix_kit_manufacturing/web/machine_type_template_live.ex#L1)

Hidden-route mini-editor for a single `machine_type` entity's
`field_template` — the list of dynamic `metadata` fields rendered on the
machine form for machines linked to that type (see
`Machines.merged_field_template/1`).

## Why this exists

`machine_type` CRUD moved to the generic `phoenix_kit_entities` admin UI
as part of the entities migration (`dev_docs/ENTITIES_MIGRATION_SPEC.md`),
but that generic form only renders `fields_definition`-declared fields —
`field_template` is deliberately *not* declared there (declaring it would
expose a raw, unvalidated JSON-array editor with none of this module's
row-level validation). It also isn't stored in `data` at all: the generic
form's `Multilang.put_language_data/3` fully replaces the primary-language
`data` block with only its declared fields on every save, which would
silently drop an undeclared key living there. So `field_template` lives in
`metadata["field_template"]` instead (see `EntitiesRegistry`'s "Record
shape" moduledoc section), a column the generic entities form never
touches — and this small standalone LiveView is the only way to edit it.

## Route

`manufacturing/machine-types/:uuid/template`, `visible: false` in
`PhoenixKitManufacturing.admin_tabs/0` — never appears in the sidebar.
`:uuid` is the `machine_type` entity-data record's own uuid. Reachable
from a pencil icon next to each type badge on `Web.MachineFormLive`'s
General tab, or by direct URL (`Paths.machine_type_template/1`).

## Row shape & validation

Reuses the row shape and per-row validation rules of the pre-migration
`machine_type_form_live.ex` (removed in E12): each row is a string-keyed
map with `key` (`~r/^[a-z0-9_]+$/`, must be unique within the template),
`label`, `type` (`text`/`number`/`date`/`boolean`/`select`), an optional
`unit`, an optional `required` flag, and — mandatory and non-empty only
when `type == "select"` — an `options` list. Unlike the removed schema's
version, validation here is plain Elixir (`validate_rows/1`), not an Ecto
changeset — `field_template` is a nested array inside `EntityData`'s
freeform `metadata` map, not a field of its own to `cast/3`.

`@field_template_rows` is a plain assign, not changeset-bound: every row
input uses a raw `name="field_template[IDX][key]"` (not `@form[:atom]`),
refreshed from submitted params on every `phx-change="validate"`, same
convention `MachineFormLive` uses for its dynamic `metadata` inputs.

## Persistence

Saves via `EntityData.update/3` with only `metadata:` in the attrs —
`Map.put`ted onto the record's *existing* metadata (not replaced) so
`legacy_uuid` (and any other key living there) survives untouched. Reads
are a plain `EntityData.get/2` (not through `EntitiesRegistry` — this page
edits a single record it already knows the uuid of, so the ETS cache
buys nothing); `EntityData.update/3` broadcasts its own
`PhoenixKitEntities.Events` message, so `EntitiesRegistry` picks up the
change on its own.

`persist/2` re-reads the record via `EntityData.get/1` immediately before
merging, rather than reusing `socket.assigns.entity_data` (the mount-time
snapshot) — the session can sit open on this page for a while, and
another process (e.g. the trash flow writing `trashed_from_status`,
see `phoenix_kit_entities`) may have written a different `metadata` key
in the meantime. Merging onto a fresh read avoids clobbering that
concurrent write with the stale snapshot's copy of the same key.

# `on_mount`

---

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