Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 824d1d0ebb0fae955ca5052db3f3489400f082f0)
+++ src/config/database.js	(revision 76290bf80c10aee5bb719e7e1e1c056970316e3e)
@@ -834,4 +834,5 @@
       content TEXT,                 -- sanitized HTML snippet of the mentioning note
       published TEXT,
+      in_reply_to TEXT,             -- de note waarop dit een antwoord is (AS2 inReplyTo)
       created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
       UNIQUE(slug, object_uri)
@@ -987,4 +988,10 @@
   ensureColumn('ap_mentions', 'quote_json', 'TEXT');        // FEP-044f quoted post
   ensureColumn('ap_mentions', 'embed_json', 'TEXT');        // external link preview
+  // AS2 inReplyTo op een vermelding/bericht. Bestond hier niet, en daarmee ging
+  // de ouder bij het opslaan verloren: elk ANTWOORD in een gesprek kwam bij de
+  // client aan alsof het een gesprek begon (Robins melding, 26-8). Bestaande
+  // rijen blijven leeg -- die ouder is niet meer te achterhalen zonder hem
+  // opnieuw op te halen, en een verzonnen ouder is erger dan geen.
+  ensureColumn('ap_mentions', 'in_reply_to', 'TEXT');
   ensureColumn('ap_interactions', 'media_json', 'TEXT');
   ensureColumn('ap_interactions', 'quote_json', 'TEXT');
Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 824d1d0ebb0fae955ca5052db3f3489400f082f0)
+++ src/routes/activitypub.js	(revision 76290bf80c10aee5bb719e7e1e1c056970316e3e)
@@ -543,4 +543,10 @@
       content: AP.stripLeadingMentions(m.content),
       url: m.note_url || undefined,
+      // Waar dit een antwoord op is (Robins melding, 26-8). Zonder dit veld
+      // kwam elk antwoord binnen als het begin van een gesprek: de client kan
+      // een keten alleen teruglopen langs inReplyTo. De andere twee legs
+      // (replyItem uit ap_interactions, sentItem via buildNote) droegen hem
+      // al -- deze was de enige die hem niet eens opsloeg.
+      inReplyTo: m.in_reply_to || undefined,
       published: AP.isoStamp(m.published || m.created_at),
       // Addressed to us and to nobody we know of: the other recipients of a
Index: src/services/ap-inbox.js
===================================================================
--- src/services/ap-inbox.js	(revision 824d1d0ebb0fae955ca5052db3f3489400f082f0)
+++ src/services/ap-inbox.js	(revision 76290bf80c10aee5bb719e7e1e1c056970316e3e)
@@ -691,7 +691,18 @@
         for (const slug of slugs) {
           try {
-            const r = db.prepare(`INSERT OR IGNORE INTO ap_mentions (slug, object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, published, help_request, wave, has_guardians, emoji_json, actor_emoji_json, media_json, created_at)
-                                  VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
-              .run(slug, o.id, safeUrl(o.url) || null, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.published || null, help ? 1 : 0, wave ? 1 : 0, hasG ? 1 : 0,
+            // De OUDER gaat mee (Robins melding, 26-8). Hij stond nergens in
+            // deze rij, dus een antwoord binnen een gesprek kwam bij de client
+            // aan alsof het een gesprek begon: de app kan een keten alleen
+            // teruglopen langs inReplyTo, en die was leeg.
+            //
+            // Alleen een http(s)-adres, langs dezelfde poort als `url`: een
+            // inReplyTo komt van een vreemde en mag geen ander schema
+            // binnensmokkelen. AS2 staat een string of een object toe, dus
+            // allebei uitpakken -- alleen de string erkennen zou hetzelfde gat
+            // laten voor iedereen die de objectvorm stuurt.
+            const ouder = safeUrl(typeof o.inReplyTo === 'string' ? o.inReplyTo : (o.inReplyTo && o.inReplyTo.id)) || null;
+            const r = db.prepare(`INSERT OR IGNORE INTO ap_mentions (slug, object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, published, in_reply_to, help_request, wave, has_guardians, emoji_json, actor_emoji_json, media_json, created_at)
+                                  VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
+              .run(slug, o.id, safeUrl(o.url) || null, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.published || null, ouder, help ? 1 : 0, wave ? 1 : 0, hasG ? 1 : 0,
                 extractEmojiTags(o.tag), emojiJsonOf(ai.emojis), mediaFromNote(o));
             if (r.changes) {
Index: src/services/ap-timeline.js
===================================================================
--- src/services/ap-timeline.js	(revision 824d1d0ebb0fae955ca5052db3f3489400f082f0)
+++ src/services/ap-timeline.js	(revision 76290bf80c10aee5bb719e7e1e1c056970316e3e)
@@ -334,5 +334,5 @@
 const MESSAGE_COLUMNS = `
       m.object_uri, m.note_url, m.actor_uri, m.actor_name, m.actor_handle, m.actor_icon, m.actor_url,
-      m.content, m.published, m.created_at, m.wave, m.help_request,
+      m.content, m.published, m.created_at, m.wave, m.help_request, m.in_reply_to,
       m.emoji_json, m.actor_emoji_json, m.media_json, m.quote_json, m.embed_json`;
 
