Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision e4119c22651ba589d6fceee5bc1802a11716f279)
+++ src/services/ActivityPubService.js	(revision 3155d8cc64d4ccbb40d068c206728faea3faee6a)
@@ -55,5 +55,5 @@
   getTimeline, replyRowsByUri, timelineRowsByIds, getReplyMessages,
   feedCursor, feedChangesSince, waitForFeedChange,
-  conversationHeads, conversationHistory, inboxPage, messageRowsByUri,
+  conversationHeads, conversationHistory, inboxPage, inboxRandomPage, messageRowsByUri,
   readMarkers, markRead, unreadPerConversation, getDirectMessages,
   isoStamp, timelineAttachments, extractEmojiTags, gateAttachments,
@@ -64,5 +64,5 @@
   getTimeline, replyRowsByUri, timelineRowsByIds, getReplyMessages,
   feedCursor, feedChangesSince, waitForFeedChange,
-  conversationHeads, conversationHistory, inboxPage, messageRowsByUri,
+  conversationHeads, conversationHistory, inboxPage, inboxRandomPage, messageRowsByUri,
   readMarkers, markRead, unreadPerConversation, getDirectMessages,
   isoStamp, timelineAttachments, extractEmojiTags, gateAttachments,
@@ -4395,5 +4395,5 @@
   getInteractions, getInteractionById, setInteractionBoosted, setInteractionLiked, buildReplyNote, getOutboxNote, getSentNotes, deliverReply, resolveRemoteNote, noteAudience, mayReadNote,
   listOutbox, deliverOutboxDelete, deliverOutboxUpdate, deliverDirectNote,
-  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, handleMoveInbox, moveAccount, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineRowsByIds, contentWarning, getDirectMessages, readMarkers, markRead, unreadPerConversation, messageRowsByUri, replyRowsByUri, conversationHeads, conversationHistory, inboxPage, isoStamp, timelineAttachments, timelineEmojis, timelineObjectLinks, timelineQuote, timelineEmbed, applyQuoteProps, deliverToActor, sendInteraction, voteOnPoll, voteOnRemotePoll,
+  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, handleMoveInbox, moveAccount, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineRowsByIds, contentWarning, getDirectMessages, readMarkers, markRead, unreadPerConversation, messageRowsByUri, replyRowsByUri, conversationHeads, conversationHistory, inboxPage, inboxRandomPage, isoStamp, timelineAttachments, timelineEmojis, timelineObjectLinks, timelineQuote, timelineEmbed, applyQuoteProps, deliverToActor, sendInteraction, voteOnPoll, voteOnRemotePoll,
   acceptGatedFollow, rejectGatedFollow, isWardGuardian, outboxAudience, sendFollowDecision,
   gateOutgoingFollow, performApprovedFollow, recordGuardianEvent, listGuardianEvents, GUARDIAN_EVENT_KEEP,
Index: src/services/ap-timeline.js
===================================================================
--- src/services/ap-timeline.js	(revision e4119c22651ba589d6fceee5bc1802a11716f279)
+++ src/services/ap-timeline.js	(revision 3155d8cc64d4ccbb40d068c206728faea3faee6a)
@@ -402,4 +402,30 @@
 }
 
+/**
+ * Een pagina vanaf een willekeurige post in je geschiedenis (shaer-ewrt).
+ *
+ * Het anker is een TIJDLIJNRIJ, geen willekeurige rij uit de unie: de app
+ * toont posts in het raster, en een greep die op een stapel berichten landt
+ * laat niets zien. Vanaf daar is het een gewone pagina over alle poten, dus
+ * `start` is een echte cursor en `next` loopt gewoon door.
+ *
+ * `rnd` is er voor de toetsen.
+ */
+export function inboxRandomPage(slug, { limit = 60, rnd = Math.random } = {}) {
+  try {
+    const { n } = db.prepare('SELECT COUNT(*) AS n FROM ap_timeline WHERE slug = ?').get(slug);
+    const k = n ? Math.min(Math.floor(rnd() * n), n - 1) : 0;
+    if (k === 0) return { ...inboxPage(slug, { limit }), start: null };
+    // De rij er net boven: de pagina begint direct daaronder, met het anker
+    // als eerste post. Zelfde stempel en volgorde als de unie.
+    const boven = db.prepare(`
+      SELECT id AS ref, ${STEMPEL('COALESCE(published, created_at)')} AS stamp
+        FROM ap_timeline WHERE slug = ?
+       ORDER BY stamp DESC, ref DESC LIMIT 1 OFFSET ?`).get(slug, k - 1);
+    const start = cursorOf(boven);
+    return { ...inboxPage(slug, { before: start, limit }), start };
+  } catch { return { rows: [], more: false, oldest: null, start: null }; }
+}
+
 // De kolommen die een bericht tot kaart maken. Een constante, want de
 // gesprekslezing haalt dezelfde rows op: twee lijsten die uiteenlopen leveren
