Changeset 6bc5ebd in Klonkt


Ignore:
Timestamp:
08/06/2026 09:19:49 AM (5 weeks ago)
Author:
Claude (agent) <aiclaude@…>
Branches:
main
Children:
7842ca1
Parents:
68a4d1b
git-author:
Robin <roboburr@…> (08/06/2026 09:19:48 AM)
git-committer:
Claude (agent) <aiclaude@…> (08/06/2026 09:19:49 AM)
Message:

Interact-pagina toonde geen reactie: twee sleutelruimtes samengetrokken

Robins melding: sound-fabrics.com/effortlesseffect is geliket EN geboost, maar
de knoppen op de interact-pagina lichtten niet op.

Een post heeft twee URI's. Zijn AP-object-id (.../ap/notes/<uuid>), die de Krant
en het C2S-pad spreken, en zijn leesbare permalink (.../effortlesseffect), waar
de interact-pagina mee binnenkomt. De tussentabel stond vol object-URI's -- alle
zestien rijen -- en de interact-pagina zocht op de permalink. De reactie bestond
dus wel; we keken op de verkeerde sleutel.

Dit is de naad die ik in fase 1 bewust open liet met de opmerking dat
samentrekken werk voor fase 2 was. Dat uitstel was fout: zodra het leespad de
tussentabel volgde, werd een bestaande reactie onzichtbaar in plaats van
inconsistent. canonicalReactionUri vertaalt nu bij zowel lezen als schrijven naar
de object-URI, met de permalink als ingang. Kennen we de post niet, dan blijft de
invoer de sleutel, zodat een reactie op iets buiten je tijdlijn blijft werken.

setReaction gebruikt daarmee EEN sleutel voor beide bronnen in plaats van de
gestuurde voor de tussentabel en de opgeloste voor de vlag.

Drie tests erbij, waaronder Robins geval bij naam: liken via de object-URI en
uitlezen via de permalink, en andersom.

LET OP BIJ UITROL ELDERS: op dev staan alle tussentabel-rijen al op de
object-URI, dus daar viel niets te migreren. Op een instance waar de
interact-pagina eerder is gebruikt kunnen rijen op de permalink staan; die worden
na deze wijziging niet meer gevonden. Meet dat vóór de uitrol:

SELECT COUNT(*) FROM ap_my_reactions r

WHERE NOT EXISTS (SELECT 1 FROM ap_timeline t

WHERE t.slug=r.site_slug AND t.id=r.target_uri);

Suite 434/434.

Co-Authored-By: Claude Opus 5 <noreply@…>

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r68a4d1b r6bc5ebd  
    34393439 */
    34403440/**
     3441 * Van wat de client stuurde naar de canonieke sleutel voor een reactie.
     3442 *
     3443 * Een post heeft twee URI's: zijn AP-object-id (.../ap/notes/<uuid>) en zijn
     3444 * leesbare permalink (.../effortlesseffect). De Krant en het C2S-pad spreken de
     3445 * eerste, de interact-pagina de tweede. Werden reacties onder allebei opgeslagen,
     3446 * dan bestond dezelfde like twee keer -- en erger: een like uit de Krant was op
     3447 * de interact-pagina onzichtbaar, want daar werd op de permalink gezocht.
     3448 *
     3449 * Dit was de naad die fase 1 bewust open liet ("samentrekken is werk voor fase
     3450 * 2"). Robin liep er meteen tegenaan: een geboost en geliket bericht toonde geen
     3451 * highlight. Vandaar hier, en niet later.
     3452 *
     3453 * De object-URI wint, want dat is waar ap_timeline op sleutelt en waar de
     3454 * backfill op is gebaseerd. Kennen we de post niet, dan blijft de invoer staan:
     3455 * een reactie op iets buiten je tijdlijn moet gewoon werken.
     3456 */
     3457export function canonicalReactionUri(slug, uri) {
     3458  if (!slug || !uri) return uri;
     3459  try {
     3460    if (db.prepare('SELECT 1 FROM ap_timeline WHERE slug = ? AND id = ?').get(slug, uri)) return uri;
     3461    const row = db.prepare('SELECT id FROM ap_timeline WHERE slug = ? AND url = ? LIMIT 1').get(slug, uri);
     3462    return (row && row.id) || uri;
     3463  } catch { return uri; }
     3464}
     3465
     3466/**
    34413467 * Wat heb IK met dit object gedaan? Leest de tussentabel, de bron van waarheid
    34423468 * sinds shaer-9e9 fase 2. Vervangt getMyReactions en getTimelineReaction, die
     
    34453471export function getReaction(slug, uri) {
    34463472  try {
    3447     const rows = (slug && uri)
    3448       ? db.prepare('SELECT kind FROM ap_my_reactions WHERE site_slug = ? AND target_uri = ?').all(slug, uri)
     3473    const key = canonicalReactionUri(slug, uri);
     3474    const rows = (slug && key)
     3475      ? db.prepare('SELECT kind FROM ap_my_reactions WHERE site_slug = ? AND target_uri = ?').all(slug, key)
    34493476      : [];
    34503477    return { liked: rows.some((r) => r.kind === 'like'), boosted: rows.some((r) => r.kind === 'boost') };
     
    34783505export function setReaction(slug, uri, kind, on, opts = {}) {
    34793506  if (!slug || !uri || (kind !== 'like' && kind !== 'boost')) return;
    3480   setMyReaction(slug, uri, kind, !!on);
    3481   const flagUri = opts.flagUri || uri;
     3507  // EEN sleutel voor beide bronnen. opts.flagUri is de opgeloste object-URI van
     3508  // de aanroeper (het C2S-pad kent die uit resolveRemoteNote en dat is
     3509  // betrouwbaarder dan onze cache); anders leiden we hem af. Vroeger kreeg de
     3510  // tussentabel de URI die de client stuurde en de vlag de opgeloste -- dat
     3511  // maakte dezelfde like onvindbaar vanaf de andere pagina.
     3512  const flagUri = opts.flagUri || canonicalReactionUri(slug, uri);
     3513  setMyReaction(slug, flagUri, kind, !!on);
    34823514  if (kind === 'boost') {
    34833515    if (!on) unmarkBoosted(slug, flagUri);
     
    48564888  gateOutgoingFollow, performApprovedFollow,
    48574889  parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs,
    4858   autoBoostCount, boostedCount, setReaction, getReaction, getReactionsFor, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
     4890  autoBoostCount, boostedCount, setReaction, getReaction, getReactionsFor, canonicalReactionUri, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
    48594891  getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
    48604892  deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
  • test/reactions-characterization.test.js

    r68a4d1b r6bc5ebd  
    193193});
    194194
    195 test('setReaction: gescheiden sleutels blijven werken zolang ze bestaan', () => {
    196   // De tussentabel wordt gesleuteld op wat de client stuurde, de vlag op de
    197   // opgeloste object-URI. Meestal gelijk, niet gegarandeerd. flagUri houdt dat
    198   // uit elkaar tot fase 2 ze samentrekt.
     195test('setReaction: flagUri bepaalt de sleutel voor BEIDE bronnen', () => {
     196  // Vroeger kreeg de tussentabel de URI die de client stuurde en de vlag de
     197  // opgeloste. Nu is het er één, anders is dezelfde like onvindbaar vanaf een
     198  // pagina die de andere URI kent.
    199199  const gestuurd = 'https://r.test/@anna/123';
    200200  const opgelost = uri('sr3');
    201201  seedTimeline(opgelost);
    202202  AP.setReaction('me', gestuurd, 'like', true, { flagUri: opgelost });
    203   assert.equal(AP.getMyReactions('me', gestuurd).liked, true, 'op de gestuurde uri');
    204   assert.equal(AP.getTimelineReaction('me', opgelost).liked, true, 'op de opgeloste uri');
     203  assert.equal(AP.getReaction('me', opgelost).liked, true, 'onder de opgeloste uri');
     204  assert.equal(AP.getTimelineReaction('me', opgelost).liked, true, 'en de vlag ook');
     205  assert.equal(AP.getMyReactions('me', gestuurd).liked, false, 'niet meer onder de gestuurde');
    205206});
    206207
     
    273274  }
    274275});
     276
     277// ── De permalink en de object-URI zijn dezelfde post ─────────────────────
     278
     279test('een like uit de Krant is zichtbaar op de interact-pagina (Robins melding)', () => {
     280  // De Krant kent een post als AP-object-URI, de interact-pagina als permalink.
     281  // Zochten die twee in verschillende sleutelruimtes, dan toonde een geboost en
     282  // geliket bericht daar geen enkele highlight -- terwijl de reactie bestond.
     283  const obj = uri('perm1');
     284  const permalink = 'https://sound-fabrics.com/effortlesseffect';
     285  db.prepare(`INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, content, url, created_at)
     286              VALUES (?,?,?,?,?,?,?)`)
     287    .run(obj, 'me', 'https://r.test/users/anna', 'Anna', '<p>x</p>', permalink, '2026-08-06 09:00:00');
     288
     289  AP.setReaction('me', obj, 'like', true);      // zoals de Krant het doet
     290  AP.setReaction('me', obj, 'boost', true);
     291  // en de interact-pagina vraagt het met de permalink:
     292  assert.deepEqual(AP.getReaction('me', permalink), { liked: true, boosted: true });
     293});
     294
     295test('andersom net zo: reageren via de permalink landt op de object-URI', () => {
     296  const obj = uri('perm2');
     297  const permalink = 'https://sound-fabrics.com/tweede';
     298  db.prepare(`INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, content, url, created_at)
     299              VALUES (?,?,?,?,?,?,?)`)
     300    .run(obj, 'me', 'https://r.test/users/bo', 'Bo', '<p>y</p>', permalink, '2026-08-06 09:00:00');
     301
     302  AP.setReaction('me', permalink, 'like', true);   // zoals de interact-pagina het doet
     303  assert.equal(AP.getReaction('me', obj).liked, true, 'onder de object-uri opgeslagen');
     304  assert.equal(AP.getTimelineReaction('me', obj).liked, true, 'dus de vlag landt ook goed');
     305  assert.equal(AP.getReaction('me', permalink).liked, true, 'en blijft vindbaar via de permalink');
     306});
     307
     308test('een reactie op iets buiten je tijdlijn blijft gewoon werken', () => {
     309  // Kennen we de post niet, dan is er niets te canoniseren en blijft de invoer
     310  // de sleutel. Een like op een vreemde post mag daar niet op stuklopen.
     311  const onbekend = 'https://elders.test/notes/xyz';
     312  AP.setReaction('me', onbekend, 'like', true);
     313  assert.equal(AP.getReaction('me', onbekend).liked, true);
     314});
Note: See TracChangeset for help on using the changeset viewer.