Ignore:
Timestamp:
08/06/2026 08:46:01 AM (5 weeks ago)
Author:
Claude (agent) <aiclaude@…>
Branches:
main
Children:
0e27e54
Parents:
02fb7ce
git-author:
Robin <roboburr@…> (08/06/2026 08:46:00 AM)
git-committer:
Claude (agent) <aiclaude@…> (08/06/2026 08:46:01 AM)
Message:

Eén schrijfpad voor reacties: setReaction (shaer-9e9 fase 1, stap 2)

Alle plekken die "ik heb hierop gereageerd" opschreven gaan nu door
setReaction(slug, uri, kind, on, opts): de tussentabel ap_my_reactions is de
waarheid, de vlaggen op ap_timeline zijn de afgeleide. Dat zijn de twee takken
van de C2S-outbox (Like/Announce en Undo) en de vier webhandlers (interact-like,
interact-boost, tijdlijn-like, tijdlijn-boost).

Twee dingen zitten er bewust in.

opts.note, omdat een boost geen vlag zet maar de post je tijdlijn IN trekt als je
de auteur niet volgt -- anders heeft de vlag geen rij om op te landen en
verschijnt de boost nergens, ook niet in de Cirkel. Dat is de valkuil die in het
plan met naam staat.

opts.flagUri, omdat de twee bronnen vandaag verschillend gesleuteld worden: de
tussentabel op de URI die de client stuurde, de vlag op de opgeloste object-URI.
Meestal gelijk, niet gegarandeerd. Die naad houdt deze stap gedragsbehoudend;
het samentrekken van de sleutels is fase 2, met datamigratie.

CORRECTIE OP HET PLAN: ik schreef dat deze stap "per definitie" geen
gedragswijziging geeft omdat lezen niet verandert. Dat klopt niet. Nu de
interact-route ook de kolom vult, verschijnt een like die eerder alleen in de
tussentabel stond voortaan ook in de Krant. Dat is de bedoelde convergentie,
maar het is wel zichtbaar -- en het hoort in de notitie te staan in plaats van
weggeredeneerd.

CORRECTIE OP DE TESTS: de karakteriseringstests zouden volgens hun eigen kop
gaan falen na deze stap. Dat gebeurde niet, en terecht: ze roepen de primitieven
aan en die zijn niet veranderd. De kop is bijgewerkt en er zijn vier tests bij
die wel bewaken wat deze stap oplevert -- dat setReaction beide bronnen vult,
dat een boost met note de rij aanmaakt en in de Cirkel belandt, dat de
gescheiden sleutels blijven werken, en dat rommelige invoer niets doet in plaats
van iets halfs.

Suite 426/426.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r02fb7ce rc010b42  
    26232623        const kind = type === 'Announce' ? 'boost' : 'like';
    26242624        await sendInteraction(site, kind, objUri, authorUri);
    2625         setMyReaction(site.slug, targetUri, kind, true);
    2626         if (type === 'Announce' && note) { try { upsertBoostedNote(site.slug, note); } catch { /* non-fatal */ } }
     2625        // Eén schrijfpad (shaer-9e9): tussentabel + afgeleide vlag in één keer.
     2626        // De note gaat mee zodat een boost de post je tijdlijn in trekt.
     2627        try { setReaction(site.slug, targetUri, kind, true, { flagUri: objUri, note: type === 'Announce' ? note : null }); }
     2628        catch { /* non-fatal: een reactie mag nooit de bezorging blokkeren */ }
    26272629        // Een Like uit een app moet ook in ap_timeline.liked landen, want dat
    26282630        // is wat de C2S-tijdlijn als shaer:liked teruggeeft. Zonder dit werd
     
    26342636        // in je tijdlijn te trekken, dus staat de post er niet in, dan is dit
    26352637        // terecht een no-op.
    2636         if (type === 'Like') { try { markLiked(site.slug, objUri); } catch { /* non-fatal */ } }
    26372638        return { status: 202, url: objUri };
    26382639      }
     
    26832684          const objUri = (note && note.object_uri) || innerTarget;
    26842685          await sendInteraction(site, kind, objUri, note && note.actor_uri);
    2685           setMyReaction(site.slug, innerTarget, innerType === 'Announce' ? 'boost' : 'like', false);
    2686           if (innerType === 'Announce') { try { unmarkBoosted(site.slug, objUri); } catch { /* non-fatal */ } }
    2687           if (innerType === 'Like') { try { unmarkLiked(site.slug, objUri); } catch { /* non-fatal */ } }
     2686          try { setReaction(site.slug, innerTarget, innerType === 'Announce' ? 'boost' : 'like', false, { flagUri: objUri }); }
     2687          catch { /* non-fatal */ }
    26882688          return { status: 202, url: objUri };
    26892689        }
     
    34113411  try { if (!_unmarkLike) _unmarkLike = db.prepare('UPDATE ap_timeline SET liked = 0 WHERE slug = ? AND id = ?'); _unmarkLike.run(slug, noteId); } catch { /* ignore */ }
    34123412}
     3413/**
     3414 * Zet een reactie van JOU op een object. Dit hoort het enige schrijfpad te zijn
     3415 * (shaer-9e9): de tussentabel ap_my_reactions is de waarheid, de vlaggen op
     3416 * ap_timeline zijn de afgeleide. Zolang markLiked en broers los aanroepbaar
     3417 * blijven kan een aanroeper ze vergeten, en dat is niet hypothetisch -- precies
     3418 * dat leverde de shaer:liked-bug op (04aca12).
     3419 *
     3420 * `opts.note` is de opgeloste remote note bij een boost. Die is niet optioneel
     3421 * uit netheid: een boost moet de post je tijdlijn IN trekken als je de auteur
     3422 * niet volgt, anders heeft de vlag geen rij om op te landen en verschijnt de
     3423 * boost nergens -- ook niet in de Cirkel.
     3424 *
     3425 * `opts.flagUri` bestaat omdat de twee bronnen vandaag verschillend gesleuteld
     3426 * worden: de tussentabel op de URI die de client stuurde, de vlag op de
     3427 * opgeloste object-URI. Meestal zijn die gelijk, maar niet gegarandeerd. Deze
     3428 * naad houdt fase 1 gedragsbehoudend; het samentrekken van die twee sleutels is
     3429 * werk voor fase 2, mét datamigratie.
     3430 */
     3431export function setReaction(slug, uri, kind, on, opts = {}) {
     3432  if (!slug || !uri || (kind !== 'like' && kind !== 'boost')) return;
     3433  setMyReaction(slug, uri, kind, !!on);
     3434  const flagUri = opts.flagUri || uri;
     3435  if (kind === 'boost') {
     3436    if (!on) unmarkBoosted(slug, flagUri);
     3437    else if (opts.note) upsertBoostedNote(slug, opts.note);
     3438    else markBoosted(slug, flagUri);
     3439  } else if (on) markLiked(slug, flagUri);
     3440  else unmarkLiked(slug, flagUri);
     3441}
     3442
    34133443export function getTimelineReaction(slug, noteId) {
    34143444  try { const r = db.prepare('SELECT liked, boosted FROM ap_timeline WHERE slug = ? AND id = ?').get(slug, noteId); return { liked: !!(r && r.liked), boosted: !!(r && r.boosted) }; } catch { return { liked: false, boosted: false }; }
     
    47794809  gateOutgoingFollow, performApprovedFollow,
    47804810  parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs,
    4781   autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
     4811  autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, setReaction, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
    47824812  getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
    47834813  deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
Note: See TracChangeset for help on using the changeset viewer.