// Mentions — being named in someone else's post.
//
// The codec already does its half: `[@alice](favela://<identity-root>)` produces a `#mention` facet
// carrying the target exactly as written, and a bare `@alice` produces plain text with NO facet
// (`markdown-strip.md` §3.4). What was missing is anything that CONSUMES them, so being mentioned
// was invisible.
//
// ⛔ The target is the identity ROOT, never a feed key and never a DID. `markdown-strip.md` §3.4 was
// revised on 2026-09-05 for exactly this: a DID in canonical bytes makes rotating one a rewrite of
// every record that ever named you, which `record-vocabulary.md` §8 forbids. The root is the one
// thing that does not rotate.
//
// ⚠ There is no mention EDGE. A mention lives in the body of a post, so finding mentions is a scan
// of what you hold — the same class of work as `search`, and it is stated rather than indexed away.
import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import { join } from '#node:path'
import Corestore from 'corestore'
import { RecordStore } from '../lib/record-store.js'
import { Feed } from '../lib/feed-base.js'
import { Peer, POST } from '../lib/peer.js'
import { identity } from './helpers.js'
const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'favela-men-'))
const rec = (body, createdAt = '2026-09-07T00:00:00Z') => ({ frontmatter: { createdAt }, body })
// A feed founded by a KNOWN identity, so its root is something a mention can name.
async function mkPeer () {
const id = await identity()
const d = await id.device()
const f = new Feed(new Corestore(tmp()), null, { store: new RecordStore(tmp()) })
await f.ready()
await f.addDevice(d.bind(f.deviceKey))
await f.update()
const p = await new Peer(f).ready()
return { peer: p, root: id.rootHex }
}
test('★ a mention of MY root is found; a mention of someone else is not', async (t) => {
const me = await mkPeer()
const them = await mkPeer()
t.teardown(() => Promise.all([me.peer.close(), them.peer.close()]))
await them.peer.feed.put(POST, 'p1', rec('hello [@me](favela://' + me.root + ') how are you'))
await them.peer.feed.put(POST, 'p2', rec('unrelated [@other](favela://' + them.root + ')'))
await them.peer.update()
await me.peer.follow(them.peer.feed.key, them.peer.feed)
await me.peer.update()
const mine = await me.peer.myMentions()
t.alike(mine.map((m) => m.name), ['p1'], 'only the one that names me')
t.is(mine[0].author, them.peer.feed.key.toString('hex'), 'and it says who said it')
})
test('⛔ a BARE @name is not a mention — never a guess', async (t) => {
const me = await mkPeer()
const them = await mkPeer()
t.teardown(() => Promise.all([me.peer.close(), them.peer.close()]))
await them.peer.feed.put(POST, 'p1', rec('hey @me, no link here'))
await them.peer.update()
await me.peer.follow(them.peer.feed.key, them.peer.feed)
await me.peer.update()
// `markdown-strip.md` §3.4: a mention resolves or is omitted. Guessing who `@me` meant is how a
// notification list starts telling people they were named when they were not.
t.alike(await me.peer.myMentions(), [])
})
test('a mention in MY OWN post about myself still counts as authored by me', async (t) => {
const me = await mkPeer()
t.teardown(() => me.peer.close())
await me.peer.feed.put(POST, 'p1', rec('note to [@self](favela://' + me.root + ')'))
await me.peer.update()
const mine = await me.peer.myMentions()
// Not filtered out here. Whether to SHOW yourself mentioning yourself is a rendering decision,
// and the projection stays honest about what it found.
t.is(mine.length, 1)
t.is(mine[0].author, me.peer.feed.key.toString('hex'))
})
test('⚠ mentions come only from feeds I HOLD', async (t) => {
const me = await mkPeer()
const them = await mkPeer()
t.teardown(() => Promise.all([me.peer.close(), them.peer.close()]))
await them.peer.feed.put(POST, 'p1', rec('[@me](favela://' + me.root + ')'))
await them.peer.update()
// No follow, no hold. ⛔ This is the structural limit of the whole model: an interaction lives in
// its author's feed, so someone you have no copy of can name you and you will never know. The
// doorbell is the one thing a stranger CAN do.
t.alike(await me.peer.myMentions(), [], 'a stranger naming me is invisible, and that is structural')
})