Changeset e4119c2 in Klonkt


Ignore:
Timestamp:
10/29/2026 05:35:25 PM (2 hours ago)
Author:
Bart <bart@…>
Branches:
main
Children:
3155d8c
Parents:
7ccdced
Message:

Inbox: oudere pagina's via next, voor 'Load more' (shaer-ewrt)

De volle lezing gaf vier poten van de nieuwste zestig en daaronder niets.
Nu ?before=<stempel|ref> als OrderedCollectionPage met next, afgekapt over
alle poten samen; de wortel krijgt een next erbij en verder niets.

Co-Authored-By: Claude Opus 5.5 <claude@…>

Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    r7ccdced re4119c2  
    965965    }, 'private, no-store');
    966966  }
     967  // ── Ouder dan wat je hebt (shaer-ewrt) ──────────────────────────
     968  //
     969  // De volle lezing hieronder is vier poten van de nieuwste zestig, en wat
     970  // daaronder lag was nergens te vragen: een 'load more' had geen adres. Dit is
     971  // dat adres, met een samengestelde cursor zoals bij /messages, en het antwoord
     972  // is een OrderedCollectionPage met partOf -- een deel, en dat zegt het ook.
     973  //
     974  // De pagina's volgen elkaar via `next`; de client bouwt er geen na.
     975  const inboxId = `${base}/ap/users/${auth.site.slug}/inbox`;
     976  const size = req.query.limit ? `&limit=${encodeURIComponent(String(req.query.limit))}` : '';
     977  const ouderLink = (pagina) => (pagina.more && pagina.oldest
     978    ? `${inboxId}?before=${encodeURIComponent(pagina.oldest)}${size}` : undefined);
     979  if (req.query.before) {
     980    const pagina = AP.inboxPage(auth.site.slug, { before: String(req.query.before), limit: req.query.limit });
     981    const ids = (leg) => pagina.rows.filter((r) => r.leg === leg).map((r) => r.ref);
     982    const tl = new Map(AP.timelineRowsByIds(auth.site.slug, ids('timeline')).map((r) => [r.id, r]));
     983    const mn = new Map(AP.messageRowsByUri(auth.site.slug, ids('message')).map((r) => [r.object_uri, r]));
     984    const rp = new Map(AP.replyRowsByUri(auth.site.slug, ids('reply')).map((r) => [r.object_uri, r]));
     985    const reacties = AP.getReactionsFor(auth.site.slug, [...tl.keys()]);
     986    const ctx = { base, me, myHandle, p: P };
     987    const mine = AP.selfAuthor(base, auth.site);
     988    const items = [];
     989    for (const r of pagina.rows) {
     990      if (r.leg === 'timeline') {
     991        const t = tl.get(r.ref);
     992        if (t) items.push(timelineItem(t, { p: P, reactions: reacties }));
     993      } else if (r.leg === 'message') {
     994        // Dezelfde poort als de volle lezing: een dichte berichtenpoort houdt
     995        // vreemden buiten, nooit de hulpvraag en nooit de guardians.
     996        const m = mn.get(r.ref);
     997        if (m && (messagesAllowed || m.help_request || guardianUris.has(m.actor_uri))) items.push(messageItem(m, ctx));
     998      } else if (r.leg === 'reply') {
     999        const m = rp.get(r.ref);
     1000        if (m) items.push(replyItem(m, ctx));
     1001      } else {
     1002        const n = AP.getOutboxNote(base, r.ref);
     1003        if (n) items.push(sentItem(n, { me, mine }));
     1004      }
     1005    }
     1006    return AP.sendAP(res, {
     1007      '@context': AP.AP_CONTEXT,
     1008      id: `${inboxId}?before=${encodeURIComponent(String(req.query.before))}${size}`,
     1009      type: 'OrderedCollectionPage',
     1010      partOf: inboxId,
     1011      orderedItems: items,
     1012      // Ontbreekt next, dan is de inbox op. De client hoort dat te weten zonder
     1013      // te gokken, anders kan zijn knop niet eerlijk verdwijnen.
     1014      next: ouderLink(pagina),
     1015    }, 'private, no-store');
     1016  }
    9671017  const rows = AP.getTimeline(auth.site.slug, 60);
    9681018  // Eén query voor de hele pagina (shaer-9e9 fase 2): shaer:liked komt uit de
    … …  
    10211071    totalItems: items.length,
    10221072    orderedItems: items,
     1073    // Waar het oudere begint (shaer-ewrt). Een `next` op de wortel en geen
     1074    // `first`, en dat is een afweging: de items hierboven ZIJN de eerste
     1075    // pagina, en een `first` ernaast laat een lezer die de paginaweg voorrang
     1076    // geeft -- de onze, op iOS en op Android -- bij elke verversing de hele
     1077    // inbox opnieuw in pagina's binnenhalen. `next` volgt niemand vanzelf; hij
     1078    // is er voor wie erom vraagt.
     1079    //
     1080    // De cursor komt uit dezelfde unie als de pagina's, over de nieuwste zestig
     1081    // van alle poten samen. Die zitten hier allemaal tussen, dus de volgende
     1082    // pagina sluit aan zonder gat; wat de wortel per poot dieper had komt daar
     1083    // hooguit nog een keer langs.
     1084    next: ouderLink(AP.inboxPage(auth.site.slug, { limit: req.query.limit || 60 })),
    10231085  });
    10241086  return undefined;
  • src/services/ActivityPubService.js

    r7ccdced re4119c2  
    5555  getTimeline, replyRowsByUri, timelineRowsByIds, getReplyMessages,
    5656  feedCursor, feedChangesSince, waitForFeedChange,
    57   conversationHeads, conversationHistory, messageRowsByUri,
     57  conversationHeads, conversationHistory, inboxPage, messageRowsByUri,
    5858  readMarkers, markRead, unreadPerConversation, getDirectMessages,
    5959  isoStamp, timelineAttachments, extractEmojiTags, gateAttachments,
    … …  
    6464  getTimeline, replyRowsByUri, timelineRowsByIds, getReplyMessages,
    6565  feedCursor, feedChangesSince, waitForFeedChange,
    66   conversationHeads, conversationHistory, messageRowsByUri,
     66  conversationHeads, conversationHistory, inboxPage, messageRowsByUri,
    6767  readMarkers, markRead, unreadPerConversation, getDirectMessages,
    6868  isoStamp, timelineAttachments, extractEmojiTags, gateAttachments,
    … …  
    43954395  getInteractions, getInteractionById, setInteractionBoosted, setInteractionLiked, buildReplyNote, getOutboxNote, getSentNotes, deliverReply, resolveRemoteNote, noteAudience, mayReadNote,
    43964396  listOutbox, deliverOutboxDelete, deliverOutboxUpdate, deliverDirectNote,
    4397   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, handleMoveInbox, moveAccount, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineRowsByIds, contentWarning, getDirectMessages, readMarkers, markRead, unreadPerConversation, messageRowsByUri, replyRowsByUri, conversationHeads, conversationHistory, isoStamp, timelineAttachments, timelineEmojis, timelineObjectLinks, timelineQuote, timelineEmbed, applyQuoteProps, deliverToActor, sendInteraction, voteOnPoll, voteOnRemotePoll,
     4397  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,
    43984398  acceptGatedFollow, rejectGatedFollow, isWardGuardian, outboxAudience, sendFollowDecision,
    43994399  gateOutgoingFollow, performApprovedFollow, recordGuardianEvent, listGuardianEvents, GUARDIAN_EVENT_KEEP,
  • src/services/ap-timeline.js

    r7ccdced re4119c2  
    351351}
    352352
     353/**
     354 * Een pagina van de inbox, nieuwste eerst, met een cursor (shaer-ewrt).
     355 *
     356 * De inbox-lezing is vier poten van elk de nieuwste zestig, en daaronder hield
     357 * het op: wat ouder was kon de app nergens vragen. Dit is de weg naar beneden.
     358 *
     359 * EEN LIMIET VOOR ALLE VIER SAMEN, om dezelfde reden als bij een gesprek: per
     360 * poot afkappen geeft een pagina die bij de ene poot tot vorige week reikt en
     361 * bij de andere tot gisteren, en dan valt er bij de volgende pagina iets in het
     362 * gat ertussen. Over de unie afgekapt is een pagina compleet voor zijn periode.
     363 *
     364 * Alleen referenties: welke poot, welk object, welk stempel. Het kaartwerk doet
     365 * de route met dezelfde bouwers als de verschil-lezing, zodat een kaart er
     366 * hetzelfde uitziet hoe hij ook binnenkwam.
     367 *
     368 * De poten zijn dezelfde als in de volle lezing: de berichtpoot slaat over wat
     369 * ook in de tijdlijn staat (getDirectMessages), en de stempels zijn die waarop
     370 * elke poot daar sorteert.
     371 */
     372const INBOX_UNION = `
     373  SELECT 'timeline' AS leg, t.id AS ref, ${STEMPEL('COALESCE(t.published, t.created_at)')} AS stamp
     374    FROM ap_timeline t WHERE t.slug = @slug
     375  UNION ALL
     376  SELECT 'message', m.object_uri, ${STEMPEL('COALESCE(m.published, m.created_at)')}
     377    FROM ap_mentions m
     378   WHERE m.slug = @slug
     379     AND NOT EXISTS (SELECT 1 FROM ap_timeline t WHERE t.slug = m.slug AND t.id = m.object_uri)
     380  UNION ALL
     381  SELECT 'reply', i.object_uri, ${STEMPEL('COALESCE(i.published, i.created_at)')}
     382    FROM ap_interactions i JOIN posts p ON p.id = i.post_id JOIN sites s ON s.id = p.site_id
     383   WHERE s.slug = @slug AND i.kind = 'reply'
     384  UNION ALL
     385  SELECT 'sent', o.id, ${STEMPEL('o.created_at')}
     386    FROM ap_outbox o WHERE o.site_slug = @slug`;
     387
     388export function inboxPage(slug, { before = null, limit = 60 } = {}) {
     389  try {
     390    const n = Math.min(Math.max(parseInt(limit, 10) || 60, 1), 200);
     391    const sep = String(before || '').indexOf('|');
     392    const bStamp = before && sep > 0 ? String(before).slice(0, sep) : null;
     393    const bRef = before && sep > 0 ? String(before).slice(sep + 1) : null;
     394    const rows = db.prepare(`
     395      SELECT leg, ref, stamp FROM (${INBOX_UNION})
     396       WHERE (@bStamp IS NULL OR stamp < @bStamp OR (stamp = @bStamp AND ref < @bRef))
     397       ORDER BY stamp DESC, ref DESC LIMIT @n`).all({ slug, bStamp, bRef, n: n + 1 });
     398    const more = rows.length > n;
     399    const page = more ? rows.slice(0, n) : rows;
     400    return { rows: page, more, oldest: cursorOf(page[page.length - 1]) };
     401  } catch { return { rows: [], more: false, oldest: null }; }
     402}
     403
    353404// De kolommen die een bericht tot kaart maken. Een constante, want de
    354405// gesprekslezing haalt dezelfde rows op: twee lijsten die uiteenlopen leveren
Note: See TracChangeset for help on using the changeset viewer.