{#
  One page of older turns, oldest first, plus a fresh sentinel above them when
  there is more.

  No root element, and the sentinel replaces *itself* with this whole fragment
  (`hx-swap="outerHTML"`), so each page lands exactly where the previous
  sentinel was and the next one ends up at the top again.

  The turns are rendered by `chat/_message.html`, the same template as
  everything else in the thread. That is what makes scrolling up look identical
  to scrolling down: there is no second way of drawing a message here, so there
  is nothing that could drift from it.
#}
{% if more_before %}
{#
  `hx-target="this"` is stated rather than inherited. This fragment is swapped
  into a page whose composer form carries `hx-target="#thread"`, and htmx
  resolves that by walking up the DOM -- the jobs chip has already demonstrated
  once what an unstated target does to a transcript.
#}
<div class="history-sentinel"
     hx-get="/api/messages/history?before={{ oldest_id }}"
     hx-trigger="revealed"
     hx-target="this"
     hx-swap="outerHTML"
     hx-sync="this:drop">
  <span class="text-xs faint">Loading earlier messages…</span>
</div>
{% endif %}

{% for message in messages %}
  {% with body_html = bodies.get(message.id, "") %}
    {% include "chat/_message.html" %}
  {% endwith %}
{% endfor %}
