{% 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 }}
{{ line_count }} lines · {{ char_count }} chars — click to expand
{% 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 }}
{{ clamp_body(msg.tool_input, msg.tool_input_needs_collapse, msg.tool_input_line_count, msg.tool_input_char_count) }} {% if msg.tool_result %} {{ clamp_body(msg.tool_result, msg.tool_result_needs_collapse, msg.tool_result_line_count, msg.tool_result_char_count, 'msg-text-error' if msg.is_error else '') }} {% endif %}
{% endif %}
{% endfor %}
{{ messages_pager() }}