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;
