OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge commits / 9c3a204

0.9.88: the summary is rendered, because AS2 summary is HTML
The bio is markdown and it was handed to Mastodon raw, so a bio reading "the **dev** box" arrives with the asterisks showing. This is the same defect the NOTE path exists to avoid — "a heading and a list arrive as paragraphs of literal bullet characters" — and it survived one field over because the profile path was written separately from it. Falls back to the plain text if rendering fails: a bio that will not render still says something. 238/238. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9c3a2043ae7a0c3779cbb8bf06f5940a2b8a9613
by plankatron <fleeky@gmail.com> · 2026-09-09T23:05:29.000Z

Changed files (3) +29 -2

lib/favela-source.js +10 -1
……@@ -97,8 +97,17 @@ export class FavelaSource {
9797 // now pins the shape and not just the name.
9898 const out = {}
9999 if (fm.name) out.name = fm.name
100 // ⛔ RENDERED, because AS2 `summary` is HTML and the bio is markdown. Handed over raw, a bio
101 // reading `the **dev** box` reaches Mastodon with the asterisks showing — the same defect the
102 // NOTE path was built to avoid ("a heading and a list arrive as paragraphs of literal •
103 // characters"), surviving one field over because the profile path was written separately.
100104 const bio = rec && rec.body ? String(rec.body).trim() : ''
101 if (bio) out.summary = bio
105 if (bio) {
106 let html = ''
107 try { html = String(render(bio) || '').trim() } catch { html = '' }
108 // A bio that will not render still says something; the plain text is better than nothing.
109 out.summary = html || bio
110 }
102111 // ⚠ An oid, not a URL — the bytes are already in the overlay and are not copied. This mints the
103112 // URL this bridge serves them at, exactly as an attachment does.
104113 if (fm.avatar) out.icon = { type: 'Image', url: blobUrl({ host: this.host, base: this.base, cid: fm.avatar }) }
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "ap-bridge",
3 "version": "0.9.87",
3 "version": "0.9.88",
44 "description": "ActivityPub membrane \u2014 a second projection of the self-custody atproto peer",
55 "type": "module",
66 "main": "index.js",
test/favela-source.test.js +18 -0
……@@ -166,3 +166,21 @@ test('getProfile projects a banner oid as AS2 image, independently of the avatar
166166 t.ok(onlyBanner.image, 'banner alone still projects')
167167 t.absent(onlyBanner.icon, 'and no avatar is invented')
168168})
169
170// ⛔ AS2 `summary` is HTML; the bio is markdown.
171test('the summary is rendered, not handed over raw', async (t) => {
172 const src = new FavelaSource({
173 peer: {
174 async profile () { return { frontmatter: { name: 'ash' }, body: 'the **dev** box' } },
175 async reach () { return { networks: ['fediverse'] } },
176 async ownPosts () { return { rows: [] } }
177 },
178 host: 'ap.7rnx.net',
179 base: 'https://ap.7rnx.net/ap/users/ashatron',
180 actorId: 'https://ap.7rnx.net/ap/users/ashatron/actor',
181 followersId: 'https://ap.7rnx.net/ap/users/ashatron/followers'
182 })
183 const p = await src.getProfile()
184 t.absent(p.summary.includes('**'), '⛔ or the asterisks show on every follower’s screen')
185 t.ok(/<strong>|<b>/.test(p.summary), 'markdown became HTML')
186})