{# Kanban board component (KanbanComponent in components.py; docs in manual/kanban.org). The no-JS move/quick-add forms are the baseline and stay rendered as the keyboard path; kanban-dnd.js layers drag-and-drop on the same markup and posts to the same place endpoint. #} {% if live_attrs %} {# Content sink required by the live client; the watch stream's only job is the reload-triggering done event. #} {% endif %}
{% for col in column_data %} {% set column = col.column %}

{{ column.title }} {{ column.cards|length }}

{% for kind, payload in col.entries %} {% if kind == "slot" %}
{{ csrf_field }}
{% else %} {% set card = payload %} {% set is_moving = moving and card.id == moving.id %}
{# Top strip: the grip is both the no-JS move entry point and, in JS mode, the drag handle (starting a drag never fires the click). #} {% if place_url %} {% if is_moving %} {{ t.move_cancel }} {% elif not moving %} {% endif %} {% endif %} {% if card.close_action and not moving %}
{{ csrf_field }} {% for fname, fval in card.close_action.fields.items() %} {% endfor %}
{% endif %}
{% if card.url %}{{ card.title }} {% else %}{{ card.title }}{% endif %} {% for badge in card.badges %} {{ badge }} {% endfor %}
{% if card.meta %}
{% for entry in card.meta %} {% if entry is string %}{{ entry }} {% else %}{{ entry[0] }}{% endif %} {% endfor %}
{% endif %} {% if card.people %}
{% for entry in card.people %} {% if entry is string %}{{ entry }} {% else %}{{ entry[0] }}{% endif %} {% endfor %}
{% endif %} {% if card.actions and not moving %}
{% for action in card.actions %}
{{ csrf_field }} {% for fname, fval in action.fields.items() %} {% endfor %}
{% endfor %}
{% endif %}
{% endif %} {% else %}

—

{% endfor %}
{% if col.quick_add and not moving %}
{{ csrf_field }}
{% endif %}
{% endfor %}