{% from '_pagination.html' import pager %} {# Render a potentially long text body with an Alpine-driven collapse wrapper. When ``needs_collapse`` is false the body renders as a plain
; when
true, a single toggles the ``msg-text-clamp`` class and a meta/banner
line exposes line + char counts. ``extra_class`` appends modifiers like
``msg-text-human`` / ``msg-text-error``. Inner Alpine scope uses
``expanded`` (not ``open``) to avoid shadowing the outer tool-call scope. #}
{% macro clamp_body(text, needs_collapse, line_count, char_count, extra_class='') %}
{% if needs_collapse %}
{{ text }}
{% else %}
{{ text }}
{% endif %}
{% endmacro %}
{# ``focus`` is deliberately absent from these links: it exists to jump to
one block, and keeping it would yank you back to that page on every
Prev/Next. #}
{% macro messages_page_url(p, ps) -%}
/sessions/{{ session_id }}?tab=messages&page={{ p }}&page_size={{ ps }}
{%- endmacro %}
{# The size picker is the only way back from a large page size, so the pager
renders whenever the session is big enough for the choice to matter — not
just when it actually spans pages. #}
{% set show_pager = messages_pagination.total > messages_pagination.page_sizes[0] %}
{% macro messages_pager() %}
{% if show_pager %}
{{ pager(messages_page_url, messages_pagination.page, messages_pagination.total_pages,
messages_pagination.page_size, messages_pagination.page_sizes) }}
{% endif %}
{% endmacro %}
Messages ({{ "{:,}".format(messages_pagination.total) }})
{%- if messages_pagination.total_pages > 1 %}
showing {{ "{:,}".format(messages_pagination.start) }}–{{ "{:,}".format(messages_pagination.end) }}
{%- endif %}
{{ messages_pager() }}
{% for msg in messages %}
{%- set show_class = 'kind-' ~ msg.kind -%}
{# Anchored on the request that paid, so a long-running tool that returns
after the TTL gets a divider too, not just a human pause. Emitted
*outside* the msg-row: the row's x-show filters (hide thinking / hide
tools / only human) would otherwise swallow the divider whenever the
paying reply happens to open with a thinking or tool_use block. #}
{% if msg.cache_loss_event %}
{% if msg.cache_loss_recoverable %}cache lost · {{ msg.cache_loss_gap_minutes }} min gap · ~{{ msg.cache_loss_cost }} wasted{% else %}break · {{ msg.cache_loss_gap_minutes }} min gap · no TTL recovers this{% endif %}
{% endif %}
{% if msg.kind == 'human_prompt' %}
you {{ msg.timestamp }}
{{ clamp_body(msg.text, msg.text_needs_collapse, msg.text_line_count, msg.text_char_count, 'msg-text-human') }}
{% elif msg.kind == 'subagent_prompt' %}
prompt to subagent subagent {{ msg.timestamp }}
{{ clamp_body(msg.text, msg.text_needs_collapse, msg.text_line_count, msg.text_char_count) }}
{% elif msg.kind == 'slash_command' %}
{{ msg.command_label }}
{% elif msg.kind == 'agent_text' %}
assistant{% if msg.is_sidechain %} subagent{% endif %}{% if msg.tokens_summary %} {{ msg.tokens_summary }}{% endif %} {{ msg.timestamp }}
{{ clamp_body(msg.text, msg.text_needs_collapse, msg.text_line_count, msg.text_char_count) }}
{% elif msg.kind == 'agent_thinking' %}
thinking{% if msg.is_sidechain %} subagent{% endif %}{% if msg.tokens_summary %} {{ msg.tokens_summary }}{% endif %} {{ msg.timestamp }}
{% if msg.thinking_has_more %}
{{ msg.thinking_preview }}
{% else %}
{{ msg.thinking_text }}
{% endif %}
{% elif msg.kind == 'agent_tool_call' %}
{{ msg.tool_name }}
{% if msg.tool_hint %}{{ msg.tool_hint }}{% endif %}
{% if msg.is_error %}error{% endif %}
{% if msg.exec_time %}{{ msg.exec_time }}{% endif %}
{% if msg.is_sidechain %}subagent{% endif %}
{{ msg.timestamp }}
{% endif %}
{% endfor %}
{{ messages_pager() }}