{% extends "base.html" %} {% import "_empty.html" as empty %} {% import "_icon.html" as ico %} {% block title %}{% if chat %}{{ chat.title }} · Messages{% else %}Messages{% endif %}{% endblock %} {# Messages, as chats. A 280px list of who the conversations are with, in four sections, beside a pane showing one of them with every thread merged into one time line. One template for both states: below 900px the stylesheet shows the list alone at /mesh/messages and the pane alone at a chat's address, so a phone gets two pages and the server renders one. A teammate's words stay a quoted block (.msg-quote): visibly not the app speaking, the same shape the agent hooks show them in. Yours are plain. #} {% block topbar_back %} {% if chat %}{% endif %} {% endblock %} {% block topbar_main %}

Messages

Shown to your agents, never acted on. {% endblock %} {% block help_extra %}
jk
Move between chats, in the chat list
e
Mark the focused chat read
{% endblock %} {% block content %} {% if chats.error %}
{% if refused %}
{{ refused }}
{% endif %} {% call empty.state('team', 'Messages are not set up on this device') %}

{{ chats.message }}

How this device joins a team

{% endcall %}
{% else %}
{% if said %}
{{ said }}
{% endif %} {% if refused %}
{{ refused }}
{% endif %} {% if preview %}

Send this?

To{% if preview.workspace %}everyone in {{ preview.workspace }} ({{ preview.count }} {{ "person" if preview.count == 1 else "people" }}){% else %}{% for p in preview.to %}{{ who(p) }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}
{{ preview.body }}
{% if not preview.thread_id %} {% endif %} Cancel
{% endif %} {% if not chat %} {% if compose %} {# Naming people directly is how a group chat starts; the list only shows groups that exist. #}

New message

{% for s in chats.sections %}{% for c in s.chats if c.kind == 'dm' and c.people %}{% endfor %}{% endfor %}

Plain text, up to 4 KB. You will see who it goes to before it is sent.

{% elif chats.unread %} {% call empty.state('clear', chats.unread ~ ' unread in ' ~ chats.unread_chats ~ (' chat' if chats.unread_chats == 1 else ' chats')) %}

Press j then Enter, or pick a chat.

{% endcall %} {% elif chats.muted_unread %} {# The rail badge counts these, so the pane must not call them nothing. #} {% call empty.state('clear', 'Nothing unread, except from muted senders') %}

{{ chats.muted_unread }} unread from someone you muted. It stays in their chat, and never interrupts.

{% endcall %} {% else %} {% call empty.state('clear', 'Nothing unread') %}

Messages from your team land here, and in Claude Code or Codex if you use them.

{% endcall %} {% endif %} {% else %}

{{ chat.title }}{% if chat.muted %} muted{% endif %}

{%- if chat.kind == 'ws' %}Everyone in {{ chat.workspace }} · {{ chat.people | length }} {{ 'teammate' if chat.people | length == 1 else 'teammates' }} {%- elif chat.kind == 'dm' %}{{ chat.people[0].name or chat.people[0].user_id }} · {{ handle(chat.people[0]) }} {%- else %}{% for p in chat.people %}{{ p.name or handle(p) }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif -%}

{% if chat.unread %}
{% endif %} {% set mutable = chat.people | selectattr('handle') | list %} {% if mutable %} {# Mute is per teammate on this device, through the same form the Settings page uses; `back` brings you here afterwards. #}
Mute
{% for p in mutable %}
{% if p.muted %} {% else %} {% endif %}
{% endfor %}
{% endif %}
{% for m in chat.messages %} {% if m.day %}
{{ m.day }}
{% endif %} {% if m.incomplete %}

Earlier messages in this thread are not on this device.

{% endif %}
{{ m.when }}
{% if m.mine %}you{% else %}{{ handle(m['from']) }}{% endif %}{% if m.muted %} muted{% endif %}
{% if m.reply_to %}{{ ico.icon('reply', 14) }} re “{{ m.reply_to.preview }}”{% endif %} {% if m.mine %}

{{ m.body }}

{% else %}
{{ m.body }}
{% endif %} {% if m.refs %}{% endif %}
{% if m.mine %}
{% if m.delivered %}delivered {{ m.delivered }}{% endif %}{% if m.delivered and m.queued %} · {% endif %}{% if m.queued %}queued {{ m.queued }}{% endif %} {% if m.failed %}{{ m.failed | length }} failed{% endif %}
{% for d in m.failed %}

Not delivered to {{ who(d.user) }}: {{ d.message }}

{% endfor %} {% else %}{% endif %}
{% else %} {% call empty.inside('clear', 'No messages yet') %}

Whatever you write below is previewed before it is sent.

{% endcall %} {% endfor %}
{# The composer joins the newest thread while it is under a day old, through the reply route, which decides who receives it from the thread; otherwise it starts a new thread addressed from the chat. #}
{% if not chat.reply_to %} {% if chat.workspace %} {% else %}{% endif %} {% endif %}

{% if chat.reply_to %}Continues the thread “{{ chat.reply_to.preview }}”{% else %}Starts a new thread{% endif %}

Plain text, up to 4 KB · deleted after {{ chat.retention_days }} days

{% endif %}
{% endif %} {% endblock %}