Index: src/views/pages/messages.ejs
===================================================================
--- src/views/pages/messages.ejs	(revision 72ec6a4c535a846688e8da900f308a46345d1578)
+++ src/views/pages/messages.ejs	(revision 7875a80e55bfc41b72ae6716ca804d1893175379)
@@ -25,10 +25,11 @@
 
   <div class="msg-filters" role="tablist" aria-label="<%= t('msg.title') %>">
+    <%# Vier chips, geen zes: Berichten, Gesprekken en Verzonden gingen op in
+        een enkele Gesprekken-view, waarin verzonden en ontvangen in dezelfde
+        draad staan. Activiteit en Moderatie blijven wat ze waren. %>
     <button type="button" class="msg-chip is-on" data-show="all"><%= t('msg.filter_all') %></button>
-    <button type="button" class="msg-chip" data-show="msgs"><%= t('msg.filter_msgs') %></button>
     <button type="button" class="msg-chip" data-show="conv"><%= t('msg.filter_conv') %></button>
     <button type="button" class="msg-chip" data-show="act"><%= t('msg.filter_act') %></button>
     <button type="button" class="msg-chip" data-show="mod"><%= t('msg.filter_mod') %></button>
-    <button type="button" class="msg-chip" data-show="sent"><%= t('msg.filter_sent') %></button>
   </div>
   <div class="msg-search">
@@ -71,4 +72,10 @@
   var items = [];
   function indexItem(li) {
+    // Een draad indexeren op zijn eerste bubbel zou de rest onvindbaar maken,
+    // dus daar nemen we de hele tekst; losse regels blijven gericht geindexeerd.
+    if (li.classList.contains('msg-thread')) {
+      li._search = ((li.getAttribute('data-who') || '') + ' ' + li.textContent).toLowerCase();
+      return;
+    }
     // Index once: sender + message body + linked post title + poll text.
     var body = li.querySelector('.msg-content');
@@ -82,5 +89,5 @@
   // items join the filter/search (and inherit the active chip via apply()).
   function reindex() {
-    items = list ? Array.prototype.slice.call(list.querySelectorAll('.msg-item')) : [];
+    items = list ? Array.prototype.slice.call(list.querySelectorAll(':scope > .msg-item')) : [];
     items.forEach(function (li) { if (!li._search) indexItem(li); });
   }
@@ -138,4 +145,24 @@
 
   .msg-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .55rem; }
+
+  /* Een draad is geen regel maar een blok: kop met de context, daaronder de
+     bubbels van oud naar nieuw. De verticale lijn links maakt zichtbaar dat ze
+     bij elkaar horen -- dat was precies wat ontbrak toen verzonden en ontvangen
+     in aparte chips zaten. */
+  .msg-thread { display: block; }
+  .msg-thread-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
+  .msg-thread-who { font-weight: 600; }
+  .msg-thread-count { font-size: .78em; padding: .05rem .4rem; border-radius: 999px;
+    background: color-mix(in srgb, var(--ink, #000) 10%, transparent); }
+  .msg-thread-post { display: inline-flex; align-items: center; gap: .3rem; font-size: .85em;
+    margin-inline-start: auto; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+  .msg-thread-post svg { width: 13px; height: 13px; flex: none; }
+  .msg-thread-msgs { list-style: none; margin: 0; padding: 0 0 0 .85rem; display: flex; flex-direction: column; gap: .45rem;
+    border-inline-start: 2px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); }
+  .msg-sub { display: flex; gap: .8rem; align-items: flex-start; }
+  /* Jouw eigen bijdrage staat aan de andere kant van de lijn, zodat je de
+     beurten ziet zonder namen te lezen. */
+  .msg-sub.msg-sent { flex-direction: row-reverse; text-align: end; }
+  .msg-sub.msg-sent .msg-line { justify-content: flex-end; }
 
   .msg-item { display: flex; gap: .8rem; padding: .85rem .95rem; border-radius: 14px; align-items: flex-start;
Index: src/views/partials/msg-item.ejs
===================================================================
--- src/views/partials/msg-item.ejs	(revision 72ec6a4c535a846688e8da900f308a46345d1578)
+++ src/views/partials/msg-item.ejs	(revision 7875a80e55bfc41b72ae6716ca804d1893175379)
@@ -1,2 +1,34 @@
+<% if (n.type === 'thread') { %>
+<%
+           var _tseen = (typeof seen !== 'undefined' && seen) ? seen : 0;
+           // Een draad is nieuw zodra EEN bericht erin nieuw is.
+           var _tnew = !!_tseen && (n.messages || []).some(function (m) {
+             return m.created_at && Date.parse(m.created_at) > _tseen;
+           });
+           var _tpeople = (n.people || []).map(function (p) { return p.name || p.handle || '?'; });
+           // Alleen jij aan het woord: dan is de tegenpartij het adres waaraan
+           // je schreef, niet een lege naam.
+           var _tto = ((n.messages || []).find(function (m) { return m.to_handle; }) || {}).to_handle;
+           var _tlabel = _tpeople.length ? _tpeople.join(', ') : (_tto || t('msg.you'));
+      %>
+        <li class="msg-item msg-thread<%= _tnew ? ' is-new' : '' %>" data-kind="conv" data-who="<%= (_tpeople.join(' ') + ' ' + (_tto || '')).toLowerCase() %>">
+          <div class="msg-thread-head">
+            <span class="msg-thread-who"><%= _tlabel %></span>
+            <% if (n.count > 1) { %><span class="msg-thread-count"><%= n.count %></span><% } %>
+            <% if (_tnew) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %>
+            <%# De context bij de draad: waar gaat dit gesprek over? Zonder deze
+                link is een antwoord in een lijst niet te plaatsen. %>
+            <% if (n.post) { %>
+              <a class="msg-thread-post" href="/<%= n.post.slug %>">
+                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
+                <%= n.post.title || n.post.slug %>
+              </a>
+            <% } %>
+          </div>
+          <ul class="msg-thread-msgs">
+            <% (n.messages || []).forEach(function (m) { %><%- include('msg-item', { n: m, seen: _tseen, inThread: true }) %><% }); %>
+          </ul>
+        </li>
+<% } else { %>
 <%
            var _seen = (typeof seen !== 'undefined' && seen) ? seen : 0;
@@ -13,8 +45,11 @@
              : (_t === 'mention' || _priv0) ? 'msgs'
              : 'conv';
+           // Binnen een draad is dit geen zelfstandige regel: de chip filtert op
+           // de draad, niet op de losse zinnen erin.
+           var _sub = (typeof inThread !== 'undefined' && inThread);
            var _init = String(_who || '?').replace(/^@/, '').charAt(0).toUpperCase();
            var _priv = (n.visibility === 'followers' || n.visibility === 'direct');
       %>
-        <li class="msg-item msg-<%= _t %><%= _new ? ' is-new' : '' %>" data-kind="<%= _kind %>" data-who="<%= String(_who).toLowerCase() %>">
+        <li class="<%= _sub ? 'msg-sub' : 'msg-item' %> msg-<%= _t %><%= _new ? ' is-new' : '' %>"<% if (!_sub) { %> data-kind="<%= _kind %>"<% } %> data-who="<%= String(_who).toLowerCase() %>">
           <span class="msg-av<%= _t === 'sent' ? ' msg-av-sent' : '' %>" aria-hidden="true">
             <% if (_t === 'sent') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
@@ -65,5 +100,5 @@
               <% } else { %><%= t('notif.replied') %><% } %>
               <% if (_priv) { %><span class="msg-priv" title="<%= t('msg.private_hint') %>">🔒 <%= t('msg.private') %></span><% } %>
-              <% if (n.post_slug) { %><a class="msg-post" href="/<%= n.post_slug %>"><%= n.post_title || n.post_slug %></a><% } %>
+              <% if (n.post_slug && !_sub) { %><a class="msg-post" href="/<%= n.post_slug %>"><%= n.post_title || n.post_slug %></a><% } %>
               <% if (_t === 'mention' && n.note_url) { %><a class="msg-post" href="<%= n.note_url %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
               <% if (_t === 'sent' && n.in_reply_to) { %><a class="msg-post" href="<%= n.in_reply_to %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
@@ -116,2 +151,3 @@
           </div>
         </li>
+<% } %>
