Changeset 08ab8ad in Klonkt for src/routes


Ignore:
Timestamp:
07/29/2026 11:26:00 AM (6 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
6100ce9
Parents:
6d5ce0c
Message:

De app las alleen de tijdlijn, dus een zwaai kwam nooit aan

Robin vroeg waarom zwaai-acties niet als bericht in Shaer verschijnen. De app
bouwt Berichten, gesprekken en de hulpvraag-escalaties uit een bron: de
C2S-inboxlees plus de eigen outbox. Die lees serveerde alleen ap_timeline, en
een directe note staat in ap_mentions. Dus kwam er niets binnen: geen zwaai,
geen DM, en sinds gisteren ook geen hulpvraag meer.

Tot d9ad6c5 lekten directe notes toevallig in de tijdlijn: de insert vroeg
alleen of het een top-level post was van iemand die je volgt. Dat hebben we
dichtgezet om hulpvragen uit de Krant te houden, en daarmee viel de enige weg
weg waarlangs de app ze binnenkreeg. Je eigen antwoord zag je nog wel, want dat
komt uit je outbox, en daardoor leek het half te werken.

Nu serveert de inboxlees allebei: posts en de directe notes die aan jou gericht
zijn. In dezelfde vorm, dus de client heeft er een parser voor. Met to:[jij] en
een Mention-tag, want zonder die adressering groepeert de app het niet tot een
gesprek, en met shaer:wave en shaer:helpRequest zodat een zwaai er als een
zwaai uitziet.

Onderweg gevonden: SQLite schrijft CURRENT_TIMESTAMP in UTC zonder zone, en
Date.parse leest dat als lokale tijd. Twee uur verschil is genoeg om een
gesprek in de verkeerde volgorde te zetten, dus dat gaat nu door isoStamp.

Changed files:
src/services/ActivityPubService.js

  • getDirectMessages(): de mentions die niet ook een tijdlijnrij zijn, want een publieke mention van iemand die je volgt staat in allebei
  • isoStamp(): een opgeslagen stempel als echt moment
  • stripLeadingMentions op de default export, die had de route nodig

src/routes/activitypub.js

  • de inboxlees serveert posts en berichten in een collectie, nieuwste eerst
  • adressering, wave-vlag, byline en de gated shaer:embed op elk bericht

New file:
test/c2s-messages.test.js

  • de twee tabellen blijven gescheiden, de lees brengt ze samen
  • de zwaai zit erin, is gemarkeerd, en is aan jou gericht
  • een publieke mention komt een keer langs, als post

remarks: 333 tests groen. De clientkant zit in de app-repos.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    r6d5ce0c r08ab8ad  
    161161  const playbackAllowed = embedsAllowed
    162162    && Guardianship.externalPlaybackAllowed(auth.site.external_playback, isWard);
    163   const items = AP.getTimeline(auth.site.slug, 60).map((t) => ({
     163  const posts = AP.getTimeline(auth.site.slug, 60).map((t) => ({
    164164    id: `${t.id}#create`,
    165165    type: 'Create',
     
    217217    },
    218218  }));
     219  // The direct notes addressed to this account: a plain DM, a guardian's wave
     220  // (§5), a ward's 🛟 help request (§5.2.1). Those are messages, not posts, so
     221  // they are not in the timeline; without them the app's Berichten shows only
     222  // what you said yourself. Same shape as a post, so one parser handles both.
     223  const me = AP.actorId(base, auth.site.slug);
     224  const myHandle = (() => { try { return `@${auth.site.slug}@${new URL(base).host}`; } catch { return `@${auth.site.slug}`; } })();
     225  const messages = AP.getDirectMessages(auth.site.slug, 60).map((m) => ({
     226    id: `${m.object_uri}#create`,
     227    type: 'Create',
     228    actor: m.actor_uri,
     229    published: AP.isoStamp(m.published || m.created_at),
     230    object: {
     231      id: m.object_uri,
     232      type: 'Note',
     233      attributedTo: m.actor_uri,
     234      content: AP.stripLeadingMentions(m.content),
     235      url: m.note_url || undefined,
     236      published: AP.isoStamp(m.published || m.created_at),
     237      // Addressed to us and to nobody we know of: the other recipients of a
     238      // note to several people are not ours to see, so we serve what we know.
     239      to: [me],
     240      // The Mention is how the client recognises itself as the addressee and
     241      // groups the note into a conversation. No FEP-e232 link tags here: a
     242      // mention row keeps the resolved quote, not the raw tags.
     243      tag: [{ type: 'Mention', href: me, name: myHandle }, ...(AP.timelineEmojis(m.emoji_json) || [])],
     244      attachment: AP.timelineAttachments(m.media_json),
     245      // FEP-633c: what kind of message this is. The wave is a gentle nudge from
     246      // a guardian; the help request is the buoy. Both render differently.
     247      'shaer:wave': m.wave ? true : undefined,
     248      'shaer:helpRequest': m.help_request ? true : undefined,
     249      'shaer:quote': AP.timelineQuote(m.quote_json),
     250      'shaer:author': (m.actor_name || m.actor_handle || m.actor_icon) ? {
     251        name: m.actor_name || undefined, handle: m.actor_handle || undefined,
     252        icon: m.actor_icon || undefined, url: m.actor_url || undefined,
     253        emojis: (() => { try { return m.actor_emoji_json ? JSON.parse(m.actor_emoji_json) : undefined; } catch { return undefined; } })(),
     254      } : undefined,
     255      'shaer:embed': embedsAllowed ? AP.timelineEmbed(m.embed_json, { playback: playbackAllowed }) : undefined,
     256    },
     257  }));
     258  // Newest first over both, so the app can keep treating this as one feed.
     259  const items = [...posts, ...messages].sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
    219260  AP.sendAP(res, {
    220261    '@context': AP.AP_CONTEXT,
Note: See TracChangeset for help on using the changeset viewer.