{% extends "base.html" %} {% block title %}{{ page_title }}{% endblock %} {% block content %} {# Outside the scan form, so the form's inherited hx-disabled-elt cannot reach it. #checks-strip is deliberately not a live region: a health list that spoke on its own would talk over the scan. #checks-body is the swap target; #checks-strip is the container that survives every swap. #}

System status

{% include "partials/checks.html" %}

Scan

{# hx-disinherit is mandatory: hx-disabled-elt is inherited, so without it every select and refresh button in this form would disable Scan for its own round-trip. method and action make a JavaScript-off submit a POST, which keeps the title out of the URL. #}
{# The description swap is innerHTML, never outerHTML: the slot's id (the aria-describedby target), its live region and its adjacency to the select must survive. The option value is the profile name, the wire contract POST /api/scan takes. autocomplete is off because a browser that restores another profile on reload or Back fires no change, so the description, ticks and correspondent would stay on the old one. #} {{ selected_description }} {# The partial names its own hx-target and hx-swap, so it does not inherit this form's status-area target. #} {% include "partials/multi_page_field.html" %} {# Each control's help is an adjacent the control names in aria-describedby, so a screen reader reads it with the control. #} What this document should be called in paperless-ngx. {# The filter input belongs to tag-filter-form through its form attribute, so its text never rides with a scan and Enter filters instead of scanning. input plus search, not keyup: a paste or dictation changes the value with no key, and the clear button fires search. The hx-include is needed on a GET, where htmx gathers no form, and carries the current ticks so a filter or refresh never unticks anything. The refresh button sits beside the legend, never inside it, because everything in a legend is part of the group's name. Its swap targets the list, never the button, so focus stays on the button. With the config key off the block is absent from the markup, not hidden by CSS. #} {% if show_tags %}
Tags Type to narrow the list. Ticked tags stay ticked. {% include "partials/tags.html" %} {# The markers name no profile until the lazy list load answers: until then the lists hold no ticks, and a Scan released early must file the profile's defaults, not nothing. #} {% with marker_id = "tags-profile", marker_name = "tags_profile", marker_profile = lists_loading_marker %}{% include "partials/profile_marker.html" %}{% endwith %}
Labels to file this under in paperless-ngx. Optional. {% endif %} {# The refresh button sits beside the label, never inside it: everything in a label is part of the select's name. Its swap targets the select's options, never the button, so focus stays on the button. The refresh includes the current choice, so refreshing never changes it. #} {% if show_correspondent %}
{% with help_elsewhere = true %}{% include "partials/correspondent_select.html" %}{% endwith %} {% include "partials/correspondent_help.html" %} {# After the help line, never between it and the select: the select has to stay the element directly before its help line. #} {% with marker_id = "correspondent-profile", marker_name = "correspondent_profile", marker_profile = lists_loading_marker %}{% include "partials/profile_marker.html" %}{% endwith %} {% endif %} {% include "partials/scan_button.html" %} {# Visible text directly after the button: a disabled button cannot be focused, so aria-describedby or a tooltip on it is never announced. Do not swap disabled for aria-disabled to keep it focusable; the server alone decides the real disabled state. #} {% if scan_blocked %}{{ scan_blocked_reason }}{% endif %} {# The lazy list load empties this out of band, byte-empty so the :empty rule in app.css hides it. app.css also hides it once the Scan button before it is enabled, because a status poll can release Scan first. #} {% if lists_loading and not scan_blocked %}{{ scan_hold_reason }}{% endif %} {# Outside the scan form, so it inherits nothing from it. every 20s retries a first request lost to htmx's timeout, which leaves the element in place and would otherwise hold Scan for good. hx-sync this:replace aborts the answer for the previous profile, which would otherwise land after the profile-change swaps and restore that profile's ticks. #} {% if lists_loading %}
{% endif %} {# Fired by a list's hidden retry once the list can be loaded; each asks again carrying the form's current filter, ticks or choice. hx-sync abort on the list's id drops a recovery while the profile-change refresh is in flight: landing first, it would take that refresh's swap target and leave the old profile's list under the new marker. #} {% if show_tags %}
{% endif %} {% if show_correspondent %}
{% endif %} {# The filter input's own form, so its text never rides with a scan. Adding hx-params to the scan form instead would be inherited by every control in it unless hx-disinherit listed it too. #} {% if show_tags %}
{% endif %}
{# #status-live is never a swap target: a screen reader announces changes to a region already on the page, not one that arrives with the change. Every status response swaps #status-area inside it, and #status-message stays its immediate previous sibling. #}
{% include "partials/status.html" %}

Job History

{% include "partials/history.html" %}
Time Profile Title Status
{% endblock %}