Changeset e9c9ae1 in Klonkt for test


Ignore:
Timestamp:
07/19/2026 10:48:15 PM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
ff3b8ce
Parents:
5190152
git-author:
Robin <roboburr@…> (07/19/2026 10:47:10 PM)
git-committer:
Robin <roboburr@…> (07/19/2026 10:48:15 PM)
Message:

Feature: mentions bar with conversation partners (klonkt-demo-u02)

Implicit mentions leave the text and become an editable "To:" bar above the
reply editor: the parent author plus the thread's ancestor authors as chips.
Removing a chip stops addressing that partner (no mention anchor, no Mention
tag, no inbox ping); explicit @mentions typed in the text keep working via the
existing resolveMentionsInText path.

  • getInteractions: each thread node gets "participants" (author + ancestor chain via the byId map, own nodes skipped, deduped, capped at 8) and carries actor_uri now.
  • reply-editor partial: the bar renders server-side with a hidden "mentions" JSON field carrying the full list, so the no-JS path addresses everyone; the JS removes chips and mirrors the remaining list into the field.
  • deliverReply({mentions}): undefined = legacy parent-only behavior; an array (possibly empty) = the kept list drives the mention prefix, the Mention tags (mentionTags reads the content anchors) and the per-actor inbox pings. to_actor/to_handle follow the kept list (parent when kept, else the first chip, else null -> the note addresses Public only).
  • deliverOutboxUpdate: an edit reuses the OLD content's leading mention anchors instead of rebuilding just to_actor, so co-mentions survive edits (legacy rows fall back as before).
  • Interact page passes the target author as the single chip. Full-screen mobile keeps the top bar above the mentions bar (flex order).

4 new tests (participant chains, multi-chip tags + to_actor, empty bar goes
Public-only, co-mentions survive edits); 97 green. Browser-verified: bar shows
@bob + @alice on a nested reply, removing @alice updates the hidden field, the
sent reply mentions and addresses only @bob; no console errors.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • test/rich-reply.test.js

    r5190152 re9c9ae1  
    144144  assert.equal(row.language, 'nl');
    145145});
     146
     147test('participants: node author plus ancestor chain, deduped, self skipped', () => {
     148  db.prepare(`INSERT INTO ap_interactions (post_id, kind, actor_uri, actor_handle, actor_url, content, object_uri, parent_uri, visibility)
     149              VALUES ('p1','reply','https://a.test/u/alice','@alice@a.test','https://a.test/@alice','<p>top</p>','https://a.test/n/1',NULL,'public')`).run();
     150  db.prepare(`INSERT INTO ap_interactions (post_id, kind, actor_uri, actor_handle, actor_url, content, object_uri, parent_uri, visibility)
     151              VALUES ('p1','reply','https://b.test/u/bob','@bob@b.test','https://b.test/@bob','<p>kind</p>','https://b.test/n/2','https://a.test/n/1','public')`).run();
     152  const { thread } = AP.getInteractions('p1', 'https://klonkt.test', site);
     153  const top = thread.find((n) => n.noteId === 'https://a.test/n/1');
     154  const child = top.children.find((n) => n.noteId === 'https://b.test/n/2');
     155  assert.deepEqual(top.participants.map((p) => p.uri), ['https://a.test/u/alice']);
     156  assert.deepEqual(child.participants.map((p) => p.uri), ['https://b.test/u/bob', 'https://a.test/u/alice']);
     157  assert.equal(child.participants[1].handle, '@alice@a.test');
     158});
     159
     160test('kept mentions: prefix carries every chip, tags follow, to_actor = parent', async () => {
     161  const r = await AP.deliverReply(site, {
     162    postId: 'p1', postSlug: 'hallo', parent, text: 'hoi allebei',
     163    mentions: [
     164      { uri: parent.actor_uri, url: parent.actor_url, handle: parent.actor_handle },
     165      { uri: 'https://b.test/u/bob', url: 'https://b.test/@bob', handle: '@bob@b.test' },
     166    ],
     167  });
     168  const row = db.prepare('SELECT * FROM ap_outbox WHERE id = ?').get(r.id);
     169  assert.equal(row.to_actor, parent.actor_uri);
     170  const note = AP.buildReplyNote('https://klonkt.test', site, row);
     171  const mentionTags = note.tag.filter((t) => t.type === 'Mention');
     172  assert.deepEqual(mentionTags.map((t) => t.href).sort(), [parent.actor_uri, 'https://b.test/u/bob'].sort());
     173});
     174
     175test('parent removed from the bar: no mention prefix for it, note goes public-only', async () => {
     176  const r = await AP.deliverReply(site, {
     177    postId: 'p1', postSlug: 'hallo', parent, text: 'zonder ping', mentions: [],
     178  });
     179  const row = db.prepare('SELECT * FROM ap_outbox WHERE id = ?').get(r.id);
     180  assert.equal(row.to_actor, null);
     181  assert.doesNotMatch(row.content, /u-url mention/);
     182  const note = AP.buildReplyNote('https://klonkt.test', site, row);
     183  assert.deepEqual(note.to, ['https://www.w3.org/ns/activitystreams#Public']);
     184  assert.equal(note.tag.filter((t) => t.type === 'Mention').length, 0);
     185});
     186
     187test('editing a multi-mention reply keeps every co-mention', async () => {
     188  const r = await AP.deliverReply(site, {
     189    postId: 'p1', postSlug: 'hallo', parent, text: 'multi',
     190    mentions: [
     191      { uri: parent.actor_uri, url: parent.actor_url, handle: parent.actor_handle },
     192      { uri: 'https://b.test/u/bob', url: 'https://b.test/@bob', handle: '@bob@b.test' },
     193    ],
     194  });
     195  const upd = await AP.deliverOutboxUpdate(site, r.id, '', { html: '<p>aangepast</p>' });
     196  assert.ok(upd && upd.ok);
     197  const row = db.prepare('SELECT * FROM ap_outbox WHERE id = ?').get(r.id);
     198  assert.match(row.content, /aangepast/);
     199  const note = AP.buildReplyNote('https://klonkt.test', site, row);
     200  assert.deepEqual(note.tag.filter((t) => t.type === 'Mention').map((t) => t.href).sort(),
     201    [parent.actor_uri, 'https://b.test/u/bob'].sort());
     202});
Note: See TracChangeset for help on using the changeset viewer.