Vibefed kb-fediverse-long-form-text
git clone https://github.com/reiver/vibefed
T=$(mktemp -d) && git clone --depth=1 https://github.com/reiver/vibefed "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/kb-fediverse-long-form-text" ~/.claude/skills/reiver-vibefed-kb-fediverse-long-form-text && rm -rf "$T"
skills/kb-fediverse-long-form-text/SKILL.mdFediverse Long-form Text (FEP-b2b8) — Complete Reference
Overview
Long-form text — blog posts, magazine articles, forum posts — is represented in ActivityPub using the
Article type, defined in the Activity Streams 2.0
vocabulary. FEP-b2b8 (authored by Evan Prodromou, received 2024-11-07, status
DRAFT) collects and extends guidance for publishers and consumers of
multi-paragraph text objects.
The core problem FEP-b2b8 addresses: the fediverse grew around microblogging where
Note dominates. Mastodon and similar clients either display Article
objects as just a title + link or strip their HTML, providing a degraded
experience. Many publishers worked around this by sending Note instead,
causing overly-long posts in microblog feeds. FEP-b2b8 defines how to
structure Article objects properly and introduces the preview property as
a fallback for microblogging clients.
1. Article vs. Note — When to Use Which
The Activity Vocabulary defines:
— a short written work, typically less than a single paragraphNote
— any kind of multi-paragraph written workArticle
Publishers SHOULD use
Article for multi-paragraph content. The common
workaround of stuffing long content into a Note causes problems for
consumers that expect Notes to be short. FEP-b2b8 explicitly discourages it:
Publishers should avoid this workaround, and instead give consumers the full information they need to display the content correctly in their own interfaces.
The IndieWeb community uses a pragmatic heuristic: articles have titles (a
name property), notes don't. The post-type-discovery algorithm checks
whether name exists and is not simply a prefix of content.
2. The Article Object — Properties
id
idA unique HTTPS URL that resolves to the object. Equivalent to RSS 2.0
guid.
name
nameThe title. Should be plain text (no HTML entities like
&), 75–150
characters. Equivalent to RSS 2.0 title.
"name": "Long-form text with included content"
url
urlLocation of the full HTML representation. Can be:
- A single string URL
- A
object withLink
andmediaType: "text/html"href - An array of strings/Link objects for multiple representations
At least one entry should have
mediaType of text/html and protocol
https. Equivalent to RSS 2.0 link.
"url": "https://example.com/2024/11/07/long-form-text.html"
Or as a Link:
"url": { "type": "Link", "mediaType": "text/html", "href": "https://example.com/2024/11/07/long-form-text.html" }
summary
summaryA brief description, teaser, abstract, or "lede." Guidelines:
- Maximum about 500 characters
- A few sentences or a short paragraph
- Can include HTML markup
- MUST NOT include embedded media (images, video, audio)
- MUST NOT include navigation elements or affordances (favourite, like, bookmark buttons)
- MUST NOT include "Read more..." links
Equivalent to RSS 2.0
description.
Summary generation hierarchy (recommended by Evan Prodromou):
- Manual user-defined summary (preferred)
- Use full text if it is ~500 chars and a single paragraph
- Use first paragraph if it meets the rough guidelines
- Truncate with ellipsis (
) as fallback…
Implementation note (NodeBB): NodeBB allows a "magic string" (HTML comment) that power users can place in their content to manually select where the summary should end. The limit and marker are configurable per instance. After consulting with Matt Baer (WriteFreely), this approach was adopted alongside ellipsis on truncation.
Interop quirk (GoToSocial): GoToSocial treats
summary as a content
warning, not as a teaser/abstract. This means Articles with a summary
may display with a content warning gate on GoToSocial, which is not the
publisher's intent.
attributedTo
attributedToThe author(s). Can be:
A string (just the actor id):
"attributedTo": "https://example.com/evan"
An object with full author info:
"attributedTo": { "type": "Person", "id": "https://example.com/evan", "name": "Evan Prodromou", "summary": "<p>Founder of Social Web Foundation</p>", "url": "https://example.com/evan", "icon": { "type": "Image", "mediaType": "image/png", "url": "https://example.com/evan.png" } }
A Link (for authors without an AS2 representation):
"attributedTo": { "type": "Link", "href": "https://example.com/evan", "name": "Evan Prodromou" }
An array for multiple authors (strings, objects, or Links mixed).
published
publishedPublication date as
YYYY-MM-DDTHH:MM:SSZ. Equivalent to RSS 2.0 pubDate.
updated
updatedDate of last update, same format. If absent, consumers can assume the object has not been modified since
published.
image
imageA notable or representative image. Can be an
id string or Image object.
Multiple values are allowed (array), ordered by importance (most important
first). Consumers use as many or as few as needed.
content
contentThe full text of the article in HTML. The HTML MUST be a sanitized subset — no CSS, no JavaScript. Allowed elements:
| Element | Allowed Attributes |
|---|---|
| — |
| class |
through | — |
| — |
| href, rel, class |
| — |
| — |
| — |
| — |
| — |
| — |
| — |
| — |
| — |
| start, reversed |
| value |
| — |
| src, alt, title, width, height, class |
| src, controls, loop, poster, width, height, class |
| src, controls, loop, class |
| src, type |
| — |
| — |
| — |
Notable omissions:
<h1> (the title goes in name), <table>, <div>,
<figure>, <figcaption>, <details>, <summary>.
The content MUST NOT include navigation links (home page, category pages), affordance buttons (favourite, like, bookmark), or "Read more" links. Any embedded media (
<img>, <video>, <audio>) in content SHOULD also be
listed in attachment for pre-fetching.
source
sourceIf the text was originally created in a different format (e.g., Markdown), the original source for editing:
"source": { "mediaType": "text/markdown", "content": "# My Article\n\nFirst paragraph..." }
replies
repliesA URL resolving to a collection of reply objects. Replies are usually
Note
objects but can be Article or Question. Equivalent to RSS 2.0 comments.
inReplyTo
inReplyToIf the article is a reply to another ActivityPub object, a string URL or JSON object. If it is commentary on a web resource (not an AP object), a
Link object with href pointing to that resource.
attachment
attachmentAdditional media for pre-fetching without parsing
content:
"attachment": [ { "type": "Image", "id": "https://example.com/image1.jpg", "mediaType": "image/jpeg" } ]
tag
tagTwo important tag types:
- Hashtag:
{ "type": "Hashtag", "name": "example", "href": "https://..." } - Mention:
{ "type": "Mention", "href": "https://..." }
context
contextLink to a larger collection — an article series, newspaper column, blog category, magazine section. Can be an array. (For topic tagging,
tag with
Hashtag may be more appropriate.)
generator
generatorInformation about the publishing software. Usually an
Application or
Service object with id and name.
to
, cc
, bcc
, bto
, audience
toccbccbtoaudienceAddressing properties. Determine delivery targets in ActivityPub and provide access control. Publishers and consumers MUST NOT disclose Article properties to anyone except addressees and creator(s).
sensitive
sensitiveMarks content as potentially sensitive (nudity, violence, spoilers, etc.). When set, consumers should obscure the article until the user opts in.
To help users decide whether to read, display these properties (in order):
— Dublin Core subject string(s). Most informative and least likely to leak sensitive content.dcterms:subject
— Hashtag names. Less human-readable than subject.tag
— Title. Authors may leak sensitive material here.name
— Last resort. A well-written summary likely includes significant excerpts of the sensitive content.summary
Using
dcterms:subject requires extending the context:
{ "@context": [ "https://www.w3.org/ns/activitystreams", "https://purl.archive.org/miscellany", {"dcterms": "http://purl.org/dc/terms/"} ], "type": "Article", "sensitive": true, "dcterms:subject": ["Citizen Kane"] }
3. The preview
Property — Microblogging Fallback
previewThe
preview property is a key innovation in FEP-b2b8 for bridging the
Article/microblogging gap. It provides a Note that gives a well-formatted
preview of the article:
{ "type": "Article", "id": "https://example.com/article.jsonld", "name": "Long-form text with preview", "url": "https://example.com/article.html", "attributedTo": "https://example.com/evan", "summary": "<p>This is the summary.</p>", "content": "<p>Full article content here...</p>", "published": "2024-11-07T12:00:00Z", "image": { "type": "Link", "href": "https://example.com/image.jpg", "mediaType": "image/jpeg" }, "preview": { "type": "Note", "attributedTo": "https://example.com/evan", "content": "<p><strong>Long-form text with preview</strong></p><p>This is the summary.</p>", "published": "2024-11-07T12:00:00Z", "attachment": { "type": "Link", "href": "https://example.com/image.jpg", "mediaType": "image/jpeg" } } }
Rules for
preview:
- Type SHOULD be
Note - Content should combine the
(as bold text) andnamesummary - MUST NOT include a link to the HTML representation
- MUST NOT include navigation or affordance elements
- Metadata from the Article (
,attributedTo
,published
,updated
) can be repeated; consumers fall back to the Article's properties if absenttag - The Article's
may be included asimage
in the previewattachment - The preview MAY have its own
id - The consumer (not the preview) is responsible for displaying a link to the full article content
Community debate: trwnh raised concerns that
preview "muddies the
waters" by treating separate objects as unified. The mechanism remains in the
FEP but its long-term acceptance is not certain.
4. Complete Example — Minimal Article
{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Article", "id": "https://example.com/2024/11/07/my-post.jsonld", "name": "My Blog Post Title", "url": "https://example.com/2024/11/07/my-post.html", "attributedTo": "https://example.com/users/alice", "summary": "<p>A brief teaser about the post content.</p>", "content": "<p>First paragraph of the full article.</p><p>Second paragraph with more detail.</p>", "published": "2024-11-07T12:00:00Z" }
5. Complete Example — Article with External Content Only
An Article can omit
content and link to external HTML instead:
{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Article", "id": "https://example.com/2024/11/07/external.jsonld", "name": "My External Article", "url": "https://example.com/2024/11/07/external.html", "attributedTo": "https://example.com/users/alice", "summary": "<p>A brief description of the article available at the URL.</p>", "published": "2024-11-07T12:00:00Z" }
6. RSS 2.0 Property Mapping
FEP-b2b8 explicitly draws parallels to RSS 2.0 for publishers familiar with syndication:
| Article Property | RSS 2.0 Equivalent |
|---|---|
| |
| |
| |
| |
| |
| |
| |
7. How Consumers Should Display Articles
FEP-b2b8 includes UI guidance for stream-oriented interfaces:
Standard display (with image)
Show: author avatar + name,
name as headline, image as hero/thumbnail,
summary as teaser text, published date, link to url for full reading.
Without image
Same as above, minus the image. The
summary gets more visual prominence.
Without title
If
name is absent, show the summary or beginning of content directly,
similar to how a Note would appear but with a link to the full content.
Sensitive content
If
sensitive is true, obscure the article behind a content warning. Show
dcterms:subject or tag names or name to help the user decide whether
to proceed.
With preview
If the consumer does not natively support
Article, display the preview
Note instead. The consumer is responsible for adding a link to the full
article.
Fallback for consumers that only display short text
Show
name, summary, and a link to url so users can read the full
content in a browser.
8. Interoperability Matrix
Publishing platforms (send Article type)
| Platform | Sends Article | Uses summary | Uses preview | Notes |
|---|---|---|---|---|
| WordPress (AP plugin) | Yes | Yes | — | Historically sent Note as workaround; now sends Article |
| Ghost | Yes | Yes | — | Active SWF participant; Ghost 6 has full AP integration |
| WriteFreely / Write.as | Yes | Yes | — | PR for FEP-b2b8 support discussed May 2025 |
| Plume | Yes | Yes | — | Early Article adopter |
| NodeBB | Yes | Yes | — | Switches between Note/Article by length; magic string for summary breaks |
| Bonfire | Yes | Yes | — | Adopted FEP-b2b8 as technical foundation |
Consuming platforms (display Article type)
| Platform | Article display | Notes |
|---|---|---|
| Mastodon | Title + link only | First-class types are Note and Question only; Article gets degraded display |
| Threads | Full display | Announced support for long-form Ghost content (2025) |
| Pleroma/Akkoma | Inline display | Supports rich text in both Note and Article |
| GoToSocial | Inline display | Treats as content warning (not teaser) |
| Bonfire | Full display | Dedicated "Articles" feed, in-app reading |
9. The Note vs. Article History
Understanding why FEP-b2b8 exists requires knowing the history:
2017: Mastodon treated Note and Article identically, stripping most HTML from both. When this was raised, Mastodon changed Article to display only as a title + link. This created a fundamental disincentive to use Article.
2019–2023 (SocialHub discussions): Years of debate over what distinguishes Note from Article. Proposals included:
- Length-based (short vs. multi-paragraph)
- Formality-based (informal message vs. publication)
- Title-based (articles have
, notes don't — IndieWeb approach)name - Formatting-based (notes have minimal markup, articles have rich layout)
No spec-level resolution was reached.
2024: The Social Web Foundation identified long-form text as a priority. Evan Prodromou convened implementer workshops with WordPress, WriteFreely, Ghost, and others. FEP-b2b8 was submitted November 7, 2024.
2025: Threads announced Article support. Bonfire adopted FEP-b2b8. WriteFreely worked on implementation. Community debates continued around the
preview mechanism and whether types should be semantic or length-based.
10. Known Issues and Pitfalls
- Mastodon's degraded display is the #1 practical problem. Articles appear as just a title and link in Mastodon's UI, losing boosts, replies, and in-app engagement. This is why many publishers historically sent Note.
- GoToSocial summary-as-CW: GoToSocial interprets
as a content warning, not a teaser. Articles with summaries will display behind a CW gate, which is not what the publisher intended.summary - NodeBB engagement drop: When NodeBB switched from Note to Article for longer posts, engagement dropped because the truncated summary was less compelling than the full Note.
- No
in content: The allowed HTML subset starts at<h1>
. The article title belongs in<h2>
, not in the HTML content.name - No
in content: Tables are not in the allowed element set. Content with tabular data must use alternative markup or be simplified.<table> - preview debate: trwnh and others question whether
is the right mechanism, arguing it creates ambiguity about which object properties are authoritative.preview - summary length is guidance, not a hard limit: "about 500 characters" is a recommendation. NodeBB made it configurable per instance. Some implementations may use longer summaries.
11. Scope Limitations
FEP-b2b8 explicitly does NOT cover:
- Book-length or longer text
- Specific media types beyond text with embedded media
- Client-to-server (C2S) article creation workflows
- Full-text search or discovery mechanisms