{% extends "base.html" %} {% comment %} The inbox (issue #14): conversation list, thread, contact panel. SPEC §14 mandates HTMX polling rather than SSE, "every 3 s on list and open thread (ETag/304 to keep payloads near-zero when unchanged)". Two things in the inline script at the bottom of this file make that work, and both are non-obvious enough to be worth reading before editing: 1. htmx does not send If-None-Match. Without it the server has nothing to compare against and every poll is a full render. 2. htmx 2's default responseHandling swaps on 304 — its rule for 2xx is the regex [23].. , which "304" matches — so an unchanged poll would swap an empty body over the pane and blank it. Everything platform-specific about what may be sent is decided server-side by apps/messaging/compliance.py and arrives here as a sentence. There is no per-platform branching anywhere in these templates, on purpose. No inline event handlers: the CSP allows 'unsafe-eval' for Alpine's x-* expressions but NOT 'unsafe-inline', so an onclick= attribute is silently inert. {% endcomment %} {% load common_extras %} {% block title %}Inbox · DemandTrail Messaging{% endblock %} {% block page_header %} {% endblock %} {% block content %} {% comment %} $nextTick in reload() is load bearing, same as on the flow list: the filter popover assigns to the model and calls reload() in one click handler, but Alpine renders the hidden inputs on the next tick — dispatching immediately serialises the form one pick behind. The shortcut handler ignores events from a field, so typing "j" into a reply does not jump to the next conversation. {% endcomment %}
{# ── Conversation list ───────────────────────────────────────── #} {% comment %} The poller is the list itself rather than a sibling element: unlike the notification bell, there is only one target here, so a second element would just be a second request. data-poll is what the If-None-Match listener keys on, which is how a mutation's request never carries a tag. {% endcomment %}
{% comment %} The list's controls live at the top of the list they control (HANDOFF §3, Inbox): the views as underline tabs with their counts, then search and the Filter popover. One form, so the poll's hx-include carries all of it. {% endcomment %}
{% comment %} Four dropdowns became four views, and the views are underline tabs: they are the one true "which slice am I looking at" control on this page (HANDOFF §2.2 allows one). Between them they answer "everything / nothing claimed yet / mine / done" — the two parameters conversations_for already takes, asked as the questions instead of as the fields. Channel and label, the genuinely long lists, are in the Filter popover below, several of each at once. {% endcomment %}
{% for view in views %} {% endfor %}
{% filter_popover groups=filter_groups onchange="reload()" noun="conversation" icon_only=True %}
{% filter_chips groups=filter_groups onchange="reload()" %}
{% comment %} The bulk bar. Outside #inbox-list so the three-second poll cannot swap it away mid-action, and hidden until something is selected. {% endcomment %} {% if can_reply and label_options %}
{% csrf_token %}
{% endif %}
{% include "inbox/_conversation_rows.html" %}
{# ── Thread and contact panel ────────────────────────────────── #} {% comment %} One swap target for both, because they are one conversation: a reply that changes the thread can also change the contact's window status, and two targets would mean the row that opens a thread has to fill both. {% endcomment %}
{% if conversation %} {% include "inbox/_thread.html" %} {% else %} {% comment %} Nothing open yet (HANDOFF §3, Inbox): say how much is waiting, how to move through it, and offer the first thing that needs a reply rather than a blank pane. {% endcomment %}

Select a conversation

{% if unread_count %}{{ unread_count }} conversation{{ unread_count|pluralize }} need{{ unread_count|pluralize:"s," }} a reply.{% else %}You are all caught up.{% endif %} Use j and k to move through the list.

{% if conversations %} {% endif %}
{% endif %}
{% endblock %} {% block alpine_components %} {% endblock %}