{# Form body, swappable as a unit. Rendered both as the whole document (via form.html) and as the HTMX swap target on Save/Cancel/Edit. The layout's heading region owns the title / breadcrumb / subtitle; this body only carries the action buttons (which must move with the swap because they change between display/edit modes). #} {% set base = "/web/views/" ~ view.module ~ "/" ~ view.name %} {% set nav_qs = ('?' ~ list_nav_query) if list_nav_query else '' %} {% set rec_url = base ~ "/record/" ~ (record_id if record_id else 'new') ~ nav_qs %} {% set rec_edit_url = base ~ "/record/" ~ record_id ~ "/edit" ~ nav_qs if record_id else '' %} {# HTMX body swap: keep prev/next in the layout heading (top-right). #} {% if body_only and record_pager %}
{% include "_record_pager.html" %}
{% endif %}
{# Action toolbar — small, right-aligned, mode-aware. #}
{{ mode }}
{% if mode == "display" and record_id %} {% for act in header_actions or [] %} {% endfor %} {% elif mode == "edit" %} {% elif mode == "new" %} {% endif %}
{# Whole-form error banner — e.g. an ORM-level rejection during write/create (unique constraint, downstream DB error). Per-field errors get rendered next to the offending input below. #} {% if form_error %}

Could not save:

{{ form_error }}

{% endif %} {# `data-pv-autosave` opts this form into the layout-level autosave/navigation interceptor: editing inputs marks the form dirty, and any subsequent link click triggers a POST to the declared URL before letting the browser follow the link. The attribute is absent in display mode so navigation stays cheap. #}
{% for section in sections %}
{% if section.title %} {{ section.title }} {% endif %}
{% for cell in section.cells %}
{{ cell.html }}
{% if cell.error %}

{{ cell.error }}

{% endif %}
{% endfor %}
{% endfor %}