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
  • test/reactions-characterization.test.js

    r02fb7ce rc010b42  
    99// eerst is vastgeschreven.
    1010//
    11 // LET OP bij het opruimen: de tests onder "de twee bronnen lopen uiteen" horen
    12 // te GAAN FALEN zodra fase 1 (één setReaction) klaar is. Dat is geen regressie
    13 // maar het doel; werk ze dan bewust bij in plaats van ze te laten verdwijnen.
     11// LET OP: de tests onder "de twee bronnen lopen uiteen" roepen de PRIMITIEVEN
     12// aan (markLiked, setMyReaction). Die blijven bewust gescheiden -- het
     13// samenvoegen zit in setReaction, dus in de aanroepers, niet in de primitieven.
     14// Deze tests blijven daarom groen na fase 1 en beschrijven dan nog steeds iets
     15// waars: wie markLiked los aanroept, raakt de tussentabel niet. Dat is precies
     16// waarom setReaction bestaat en waarom die primitieven op termijn intern moeten
     17// worden.
     18//
     19// De invariant die fase 1 wél bewaakt staat onderaan, bij setReaction.
    1420//
    1521// Wat hier NIET in kan: de routes zelf (die vragen HTTP + sessie) en het
     
    115121
    116122// ── De twee bronnen lopen uiteen ─────────────────────────────────────────
    117 // DEZE TESTS HOREN TE FALEN NA FASE 1. Ze leggen de huidige splitsing vast,
    118 // zodat het samenvoegen een zichtbare gebeurtenis is en geen stille.
     123// De primitieven raken elkaar niet, en dat blijft na fase 1 zo: het
     124// samenvoegen zit in setReaction. Deze drie leggen vast waarom die functie
     125// moet bestaan, en waarom markLiked en broers uiteindelijk intern horen te
     126// worden -- zolang ze los aanroepbaar zijn, kan een aanroeper de helft doen.
    119127
    120128test('HUIDIG GEDRAG: de tijdlijn-route vult de tussentabel niet', () => {
     
    123131  assert.equal(AP.getTimelineReaction('me', u).liked, true, 'de kolom staat aan');
    124132  assert.equal(AP.getMyReactions('me', u).liked, false,
    125     'NA FASE 1 hoort dit true te zijn: pas deze test dan bewust aan');
     133    'de primitief raakt de tussentabel niet -- daarvoor is setReaction');
    126134});
    127135
     
    131139  assert.equal(AP.getMyReactions('me', u).liked, true, 'de tussentabel staat aan');
    132140  assert.equal(AP.getTimelineReaction('me', u).liked, false,
    133     'NA FASE 1 hoort dit true te zijn: pas deze test dan bewust aan');
     141    'de primitief raakt de kolom niet -- daarvoor is setReaction');
    134142});
    135143
     
    145153});
    146154
    147 // ── Wat de invariant-test van fase 1 straks moet bewaken ─────────────────
     155// ── De scheefheid, meetbaar (fase 0 uit shaer-9e9 als test) ─────────────
    148156
    149157test('meetbaar: hoeveel rijen hebben een vlag zonder tegenhanger', () => {
     
    157165  assert.ok(scheef > 0, 'vandaag lopen ze uiteen; na fase 1 hoort deze assert omgedraaid te worden naar === 0');
    158166});
     167
     168// ── De invariant van fase 1: setReaction houdt de bronnen gelijk ─────────
     169
     170test('setReaction: een like landt in BEIDE bronnen', () => {
     171  const u = uri('sr1'); seedTimeline(u);
     172  AP.setReaction('me', u, 'like', true);
     173  assert.equal(AP.getMyReactions('me', u).liked, true, 'tussentabel');
     174  assert.equal(AP.getTimelineReaction('me', u).liked, true, 'afgeleide vlag');
     175  AP.setReaction('me', u, 'like', false);
     176  assert.equal(AP.getMyReactions('me', u).liked, false);
     177  assert.equal(AP.getTimelineReaction('me', u).liked, false);
     178});
     179
     180test('setReaction: een boost met note trekt de post je tijdlijn in EN vult beide', () => {
     181  // De valkuil uit het plan: zonder de note bestaat de rij niet en landt de
     182  // vlag nergens, dus zou de boost onvindbaar zijn.
     183  const u = uri('sr2');
     184  AP.setReaction('me', u, 'boost', true, {
     185    note: { object_uri: u, actor_uri: 'https://r.test/users/bo', actor_name: 'Bo', content: '<p>x</p>', media: '[]' },
     186  });
     187  assert.equal(AP.getMyReactions('me', u).boosted, true, 'tussentabel');
     188  assert.equal(AP.getTimelineReaction('me', u).boosted, true, 'afgeleide vlag');
     189  assert.ok(AP.getCirkelPosts('me', 60, 0).some((p) => p.id === u), 'en zichtbaar in de Cirkel');
     190  AP.setReaction('me', u, 'boost', false);
     191  assert.equal(AP.getMyReactions('me', u).boosted, false);
     192  assert.equal(AP.getCirkelPosts('me', 60, 0).some((p) => p.id === u), false);
     193});
     194
     195test('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.
     199  const gestuurd = 'https://r.test/@anna/123';
     200  const opgelost = uri('sr3');
     201  seedTimeline(opgelost);
     202  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');
     205});
     206
     207test('setReaction: rommelige invoer doet niets in plaats van iets halfs', () => {
     208  AP.setReaction('me', uri('sr4'), 'sterretje', true);
     209  assert.equal(AP.getMyReactions('me', uri('sr4')).liked, false);
     210  AP.setReaction('', uri('sr5'), 'like', true);
     211  AP.setReaction('me', '', 'like', true);
     212  assert.equal(AP.getMyReactions('me', uri('sr5')).liked, false);
     213});
Note: See TracChangeset for help on using the changeset viewer.