{# One server's stored operations, and the line that counts them (spec §7.1). Returned on its own as the answer to a filter and to every review decision, which is why it is a template rather than a macro and why it starts at the element htmx replaces. Its context is ``operations``, ``operations_id``, ``operations_target`` and ``operations_form_id``, which is what ``routes_ui._operations_context`` builds. The filter controls are deliberately *outside* this region, as on the picker: swapping an input the operator is typing in would move the caret out from under them. The table is one form, and it is not this element. Every control in every row is bound to it by ``form="operations-form"`` — HTML's own way of saying that a control belongs to a form it is not inside — and the form element itself, with the one Save below it, lives out beside the filters. Not a form wrapped around the ````, because the last cell holds the review forms and a form inside a form is not HTML; and not inside this region, because this region is replaced on every filter and every decision (task 114). A hidden ``op_id`` per row is what says which rows were rendered. An unticked checkbox posts nothing at all, so without it a submission could not tell a row the operator unticked from a row that was never on the page — and every filter would be a bulk deselect. A filtered-out row is hidden rather than dropped, so that nothing an operator cannot see is quietly left out of the page they are working on. With one button that stops being a courtesy: it is what keeps the filter from unselecting what it hid. #} {% from "partials/status_badge.html" import status_badge %} {% from "partials/confirm.html" import confirm %}
{% if operations.alerts %} {% endif %} {% if operations.review or operations.flagged %} {# Inside the swapped region on purpose: every decision below moves these counts, and one of them takes the flag off. A strip that lived in the page chrome would go on saying "3 new" after the third one was added. #}

{% if operations.flagged %} {{ status_badge("attention", title="A refresh found changes nobody has reviewed yet.") }} {% endif %} {{ operations.review_note }}

{% for count in operations.review %} {# A link rather than a badge: the number and the filter answer the same question, and an operator who reads "3 new" wants to see those three. #} {{ count.summary }} {% endfor %} {% if operations.filter.status %} Show all {% endif %}

{% if operations.flagged %}
{% endif %}
{% endif %}

{{ operations.summary }}

{# Not "Tool name". The column now prints the Tool prefix in front of every box in it, and two headings a card apart reading "Tool prefix" and "Tool name" describe two settings rather than one name built out of the other — which is what they are. The picker's column is headed the same way, for the same reason (tasks 115 and 116). #} {% for row in operations.rows %} {% endfor %} {% if not operations.shown %} {% endif %}
{# Ships hidden and is unhidden by the script that wires it: it moves ticks in the page and writes nothing itself, so with no script it would be a box that does nothing, which is worse than an empty cell. Nothing is lost — the rows are still ticked one at a time, which is how this page has always worked (task 114). #} Method PathName Review
{# Which row this is, for a submission that cannot otherwise tell. #} {{ row.operation.method }} {# The badge the Status column used to hold, where the picker already puts a row's badges. The fact stays: the review strip's counts are links to ``?status=new`` and the selector above offers the same four, so a table that said nothing about a row's state would send an operator who followed "3 new" to three rows with no reason (task 114). #} {{ row.operation.path }} {% if row.badge %}{{ status_badge(row.badge) }}{% endif %} {# The override when there is one: it is the sentence the tool ships, and a page showing the spec's summary while the model is being told something else is a page describing somebody else's gateway. It is read-only here — the column that held it is gone, and the JSON API is where one is set (task 116). #} {% if row.note %} {{ row.note }} {% endif %} {# The server's prefix, printed rather than typed. It is a field in the Settings card on this same page and every name in this column is built out of it, so the box holds the half this row owns and the other half is shown as the fixed thing it is. Empty on a row whose stored name was never built on it, where the box holds all of the name — see ``OperationRow.prefixed`` (task 116). #} {% if row.lead %}{{ row.lead }}{% endif %} {% if row.prefix_note %} {{ row.prefix_note }} {% endif %} {# Only when the box says something else, which is a save that was refused: otherwise the box or its placeholder is already the name, and repeating it under every row of two hundred says nothing. #} {% if row.typed_name and row.typed_name != row.operation.effective_tool_name %} Still published as {{ row.operation.effective_tool_name }} {% endif %} {% if row.error %} {{ row.error }} {% endif %} {# What spec §5.4 says can be done with this row, if it is waiting on a decision. A form each, and one request each: a decision is an answer to a question the gateway asked rather than an edit to the row, so it is not part of what the button below saves. A browser with no script reviews a server the same way — the answer is then the page rather than the table, and it lands back on the same filter. The column stays even when nothing here is flagged and every cell in it is empty: a table that grows a column when a refresh finds something and loses it when the last decision is settled is a table that jumps under the operator mid-review. #} {% for decision in row.decisions %}
{% endfor %} {% if row.deletable %} {{ confirm( operations.row_path(row), "Delete", row.delete_question, target=operations_target, swap="outerHTML") }} {% endif %}
{{ operations.nothing_here }}