OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface commits / fb5b01a

0.0.66: a fediverse notification names the outside account and links out (I1 S4)
Rows from a bridge observation feed name an AP actor URI; who() would link /u/<URI>. Shown as user@host, linking out, marked (fediverse); only http(s) becomes a link. Test-first; 247/247. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fb5b01a9534ef065ec9d36e3782161464542b968
by plankatron <fleeky@gmail.com> · 2026-10-14T19:57:53.000Z

Changed files (3) +38 -2

lib/pages.js +16 -1
……@@ -967,6 +967,20 @@ export function thread (subject, replies, profiles = null, files = null, target
967967 * only feeds you HOLD can appear, so a stranger interacting with you is invisible. The doorbell is
968968 * the exception and it is in the list.
969969 */
970// ⛔ AN OUTSIDE ACTOR IS NOT A FEED (hyper-social #82, I1). A row from a bridge's observation feed names an account on
971// another network by its URI. who() would link /u/<that URI> — a favela profile that does not exist. Shown as the
972// account it is (user@host when the URI says so), linking OUT, and marked as coming from the fediverse. Only an
973// http(s) URI ever becomes a link.
974function outsideActor (uri) {
975 let url = null
976 try { url = new URL(String(uri)) } catch {}
977 if (!url || (url.protocol !== 'https:' && url.protocol !== 'http:')) return '<span class="who">' + esc(String(uri || 'someone')) + '</span> <span class="who">(fediverse)</span>'
978 const seg = url.pathname.split('/').filter(Boolean)
979 const user = seg.length >= 2 && (seg[0] === 'users' || seg[0] === 'u') ? seg[1] : (seg[0] && seg[0].startsWith('@') ? seg[0].slice(1) : null)
980 const label = user ? user + '@' + url.host : url.host + url.pathname
981 return '<a class="who" href="' + esc(url.href) + '" rel="nofollow noopener" target="_blank">' + esc(label) + '</a> <span class="who">(fediverse)</span>'
982}
983
970984export function notifications (rows, profiles = null) {
971985 const VERB = {
972986 reaction: (n) => (n.value === 'like' ? 'liked' : 'reacted ' + esc(n.value || '')) + ' your post',
……@@ -983,7 +997,8 @@ export function notifications (rows, profiles = null) {
983997 ? ' <a href="/t/' + encodeURIComponent(n.target.feed + '/' + n.target.collection + '/' + n.target.name) + '">→</a>'
984998 : ''
985999 const snippet = n.body ? '<div class="who bio">' + esc(n.body.replace(/\s+/g, ' ').trim().slice(0, 120)) + '</div>' : ''
986 return '<li>' + who(n.author, profiles) + ' ' + verb + link + snippet + '</li>'
1000 const by = n.source === 'fediverse' ? outsideActor(n.author) : who(n.author, profiles)
1001 return '<li>' + by + ' ' + verb + link + snippet + '</li>'
9871002 }).join('') + '</ul>'
9881003 : empty('No notifications.',
9891004 'These are derived from feeds you already hold — nothing is sent to you. Somebody whose feed ' +
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-surface",
3 "version": "0.0.65",
3 "version": "0.0.66",
44 "description": "The favela renderer \u2014 server-side HTML over the daemon's socket. No storage, no keys, no swarm.",
55 "type": "module",
66 "main": "index.js",
test/pages.test.js +21 -0
……@@ -887,3 +887,24 @@ test('status shows the identity root next to "linked"', async (t) => {
887887 const html = status({ me: { feed, linked: true }, profiles: { [feed]: { root } } })
888888 t.ok(html.includes(root), 'the root is on the status page')
889889})
890
891// ⛔ AN OUTSIDE ACTOR IS NOT A FEED (hyper-social #82, I1 S4). A notification from a bridge's observation feed names
892// an ActivityPub actor URI. Rendering it through who() would link /u/<that URI> — a favela profile that does not
893// exist. It is shown as the account it is, links OUT to it, and says it came from the fediverse.
894test('★ a fediverse notification names the outside account and links out to it', (t) => {
895 const html = notifications([
896 { kind: 'reaction', value: 'like', source: 'fediverse', author: 'https://prsm.space/users/alice', at: '2026-10-14T19:21:17Z', target: { feed: 'ab'.repeat(32), collection: 'post', name: 'p1' }, name: 'o1', body: '' },
897 { kind: 'reply', source: 'fediverse', author: 'https://prsm.space/users/alice', at: '2026-10-14T19:21:25Z', target: null, name: 'o2', body: 'nice post!' }
898 ])
899 t.ok(/alice@prsm\.space/.test(html), 'shown as user@host')
900 t.ok(/href="https:\/\/prsm\.space\/users\/alice"/.test(html), 'linking out to the account')
901 t.absent(/\/u\/https/.test(html), 'never as a favela profile path')
902 t.ok(/fediverse/.test(html), 'and says where it came from')
903 t.ok(/liked your post/.test(html) && /replied to your post/.test(html))
904 t.ok(/nice post!/.test(html), 'with the reply text')
905})
906
907test('⛔ a fediverse author that is not an http(s) URL is never made a link', (t) => {
908 const html = notifications([{ kind: 'follow', source: 'fediverse', author: 'javascript:alert(1)', at: '', target: null, name: 'x', body: '' }])
909 t.absent(/href="javascript/i.test(html))
910})