- Outbound Delete: broadcastDelete() + POST /admin/federation/delete route
- Visibility: unlisted + followers-only addressing via defaultVisibility config
- Content Warning: outbound sensitive flag + summary as CW text
- Polls: inbound Question/poll parsing with progress bar rendering
- Flag: inbound report handler with ap_reports collection + Reports tab
- Includes DM support files from v2.9.x (messages controller, storage, templates)
- Includes coverage audit and high-impact gaps implementation plan
Confab-Link: http://localhost:8080/sessions/cc343b15-8d10-43cd-a48f-ca912eb79b83
The reply context on the compose page rendered raw Mastodon HTML in a
bare <blockquote>, causing hashtag links to display as block elements
(each on its own line with # separated from the tag name). Wrapping
the content in a <div class="ap-card__content"> applies the same
inline hashtag/mention/invisible-span CSS rules used in the timeline.
Confab-Link: http://localhost:8080/sessions/cc343b15-8d10-43cd-a48f-ca912eb79b83
- Gallery photos: 220px → 280px height, 180px on mobile (≤480px)
- Link preview cards: full CSS for horizontal card layout (text left, image right)
- Lightbox: touch/swipe support for mobile (50px threshold)
- URL linkification: bare URLs in content auto-wrapped in <a> tags before AP delivery
Confab-Link: http://localhost:8080/sessions/c5b1471e-b046-44d9-b94f-ab5e68fae7cc
Reply links were using the AP internal object ID (e.g.
/ap/users/{id}/statuses/{id}) which returns 404 on Mastodon for
browsers. Now uses the human-readable URL (/@username/{id}) for
replyTo params in item cards and notification cards.
- Store url field on reply/mention notifications (inbox-listeners)
- Prefer item.url over item.uid for compose replyTo links
- Falls back to uid for existing notifications without url field
Confab-Link: http://localhost:8080/sessions/d116ad5b-ef8a-424e-9ebe-76c06bef1df6
Remove the quick-reply code path entirely — all replies now go through
Micropub as blog posts. Quick replies created orphan URLs that served
raw JSON-LD to browsers and caused unreadable links in conversations.
- Delete quick-reply controller (note-object.js) and route
- Remove ap_notes collection registration
- Simplify compose form: no mode toggle, no character counter
- Remove quick-reply CSS and locale strings
Confab-Link: http://localhost:8080/sessions/d116ad5b-ef8a-424e-9ebe-76c06bef1df6
Shorten long URLs in post content (30 char display limit with tooltip).
Collapse hashtag-heavy paragraphs into expandable <details> toggle.
Show BOT badge for Service/Application actors. Show pencil icon for
edited posts with hover tooltip showing edit timestamp.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Animated card-shaped placeholders with shimmer effect shown during
content loading instead of plain "Loading..." text. Applied to reader,
tag timeline, and explore tabs (both first-load and load-more states).
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Extract reply/boost/like counts from AP Collections (getReplies,
getLikes, getShares) and Mastodon API (replies_count, reblogs_count,
favourites_count). Display counts next to interaction buttons with
optimistic updates on like/boost actions.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Change photo storage from bare URL strings to objects with url, alt,
width, height (AP) plus blurhash and focus (Mastodon API). Templates
handle both old string and new object format for backward compat.
Add ALT text badges on gallery images — click to expand the full
alt text in an overlay. Renders in both reader and explore views.
Also pass alt text through to lightbox and quote embed photos.
Bump version to 2.5.3.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Add Alpine.js directive x-relative-time that converts absolute dates
to human-friendly relative strings: just now, 5m, 3h, 2d, Mar 3.
Updates every 60s for posts less than 24h old. Server-rendered absolute
time stays as no-JS fallback and hover tooltip.
Applied to item cards, quote embeds, and notification cards.
Bump version to 2.5.2.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Extract custom emoji from ActivityPub objects (Fedify Emoji tags) and
Mastodon API (status.emojis, account.emojis). Replace :shortcode:
patterns with <img> tags in the unified processing pipeline.
Emoji rendering applies to post content, author display names, boost
attribution, and quote embed authors. Uses the shared postProcessItems()
pipeline so both reader and explore views get emoji automatically.
Bump version to 2.5.1.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Extract shared item-processing.js module with postProcessItems(),
applyModerationFilters(), buildInteractionMap(), applyTabFilter(),
renderItemCards(), and loadModerationData(). All controllers (reader,
api-timeline, explore, hashtag-explore, tag-timeline) now flow through
the same pipeline.
Unify Alpine.js infinite scroll into single parameterized
apInfiniteScroll component configured via data attributes, replacing
the separate apExploreScroll component.
Also adds fetchAndStoreQuote() for quote enrichment and on-demand
quote fetching in post-detail controller.
Bump version to 2.5.0.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
- Poll every 30s for new items, show sticky "N new posts — Load" banner
- IntersectionObserver marks cards as read at 50% visibility, batches to
server every 5s
- Read cards fade to 70% opacity, full opacity on hover
- "Unread" toggle in tab bar filters to unread-only items
- New API: GET /api/timeline/count-new, POST /api/timeline/mark-read
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Extract quoteUrl from Fedify Note objects (supports Mastodon, Misskey,
Fedibird quote formats). Fetch quoted post data asynchronously on inbox
receive and on-demand in post detail view. Render as rich embed card
with author avatar, handle, content, and timestamp.
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
Replace the cramped deck/column layout on the explore page with a
tabbed interface. Three tab types: Search (always first), Instance
(pinned with local/federated badge), and Hashtag (aggregated across
all pinned instances).
- New ap_explore_tabs collection replaces ap_decks (clean start)
- Tab CRUD API: add, remove, reorder with CSRF/SSRF validation
- Per-tab infinite scroll with IntersectionObserver + AbortController
- Hashtag tabs query up to 10 instances in parallel, merge by date,
deduplicate by URL
- WAI-ARIA tabs pattern with arrow key navigation
- LRU cache (5 tabs) for tab content
- Extract shared explore-utils.js (validators + status mapping)
- Remove all old deck code (JS, CSS, controllers, locale strings)
Adds a save button to the AP item card action bar that POSTs to
/readlater/save when the readlater plugin is installed. Uses Alpine.js
for optimistic UI update. Button only renders if
application.readlaterEndpoint is set.
The base layout default.njk imports a `tag` component macro which shadows
the controller's `tag` variable in function/filter argument contexts.
Renaming to `hashtag` eliminates the collision entirely.
The i18n __() call with `tag` as second argument collided with the `tag`
Nunjucks component macro imported by default.njk. Use | replace filter
instead of sprintf-style substitution to avoid the scoping issue.
Document.njk pages (followers, following, activities, featured, tags,
profile, migrate) get parent breadcrumbs via the upstream heading
component. Reader pages (explore, notifications, compose, moderation,
tag timeline, post detail, remote profile, my profile) get a new
breadcrumb nav bar in ap-reader.njk layout.
Users can favorite instances (with local or federated scope) as persistent
columns in a multi-column deck view. Each column streams its own public
timeline with independent infinite scroll. Includes two-tab explore UI
(Search + Decks), deck CRUD API with CSRF/SSRF protection, 8-deck limit,
responsive CSS Grid layout, and scope badges.
- Add FediDB API client (lib/fedidb.js) with MongoDB caching (24h TTL)
for instance search, timeline support checks, and popular accounts
- Explore page: instance input now shows autocomplete suggestions from
FediDB with software type, MAU count, and timeline support indicator
(checkmark/cross) via background pre-check
- Reader page: @handle lookup input now shows popular fediverse accounts
from FediDB with avatar, name, handle, and follower count
- Three new API endpoints: /api/instances, /api/instance-check,
/api/popular-accounts
- Alpine.js components for both autocomplete UIs with keyboard navigation
- Fix mentions/hashtags bug: separate Fedify Mention and Hashtag types into
distinct mentions[] and category[] arrays with proper @ and # rendering
- Add hashtag timeline filtering at /admin/reader/tag with regex-safe queries
- Replace prev/next pagination with AlpineJS infinite scroll (IntersectionObserver)
with no-JS fallback pagination preserved
- Add public instance timeline explorer at /admin/reader/explore with SSRF
prevention and XSS sanitization via Mastodon-compatible API
- Add hashtag following with ap_followed_tags collection, inbox listener
integration for non-followed accounts, and followed tags sidebar display
- Include one-time migration script for legacy timeline data
Add OStatus subscribe template to WebFinger responses so remote servers
(WordPress AP, Misskey, etc.) can discover and redirect users to complete
follow interactions. Unauthenticated users are sent to login first, then
redirected to the existing reader profile page with follow/unfollow UI.
Fedify's JSON-LD compaction collapses single-element arrays to plain
objects. Mastodon checks `attachment.is_a?(Array)` and silently skips
non-array values, causing profile links to never display.
Also adds profile links section to the my-profile admin page and
fixes rel=me on the public profile page for bidirectional verification.
The | min filter is Jinja2 syntax, not available in Nunjucks. This caused
"filter not found: min" crashes when posts had photos (never triggered
before the async iteration fix because photo arrays were always empty).
Fedify 2.0's getAttachments() and getTags() return async iterables, but the
code used synchronous for...of which silently yielded zero results. Changed
to for await...of so media URLs (photo/video/audio) and hashtags are now
properly extracted from incoming posts.
Also replaced the gallery's target=_blank links with an Alpine.js lightbox
modal for full-size image viewing with prev/next navigation and keyboard
support.
Moderation page rewritten as single Alpine.js component with inline DOM
updates instead of location.reload(). Added hide/warn filter mode toggle
— warn mode shows muted items behind content warning instead of hiding.
Expanded keyword matching to check content, titles, and summaries.
Fixed MongoDB E11000 duplicate key error by dropping non-sparse indexes
on startup and recreating with sparse:true. Storage layer no longer
stores null url/keyword fields.
- Notification view: tab navigation (Replies, Likes, Boosts, Follows, All)
with count badges; defaults to Replies tab; type filter in storage layer
with compound index for efficient queries
- My Profile admin page: profile header with avatar/stats/bio, tabbed
activity view (Posts, Replies, Likes, Boosts) pulling from posts,
ap_activities, and ap_interactions collections
- Reader: default tab changed from All to Notes
- Timeline cards: timestamps now link to post detail view
- Notification cards: Reply and View Thread buttons on reply/mention types
Override upstream .mention { display: grid } that broke Mastodon's
hashtag/mention HTML in profile bios. Fixes both admin reader
(.ap-profile__bio) and public profile (.ap-pub__bio) views.
Replace the browser redirect on /activitypub/users/:handle with a
standalone HTML profile page showing avatar, bio, profile fields,
stats (posts/following/followers/joined), follow-me prompt with
copy button, pinned posts, and recent posts. Supports light/dark
mode via prefers-color-scheme. ActivityPub clients still get JSON-LD
from Fedify before this route is reached.
Adds a search box at the top of the reader page where users can paste
any fediverse URL or @user@domain handle. Uses Fedify's lookupObject()
which natively resolves URLs, handles, and acct: URIs, then redirects
to the internal post detail or remote profile view.
document.njk already renders title as h1 via the heading macro.
All 14 AP templates were also calling heading() with level 1 inside
their content block, producing two h1 elements per page. Removed
the redundant calls and moved dynamic count prefixes into the title
variable in followers/following controllers.
- Fix cursor pagination: use string comparison (not Date objects) for
published field queries in both timeline and notifications
- Fix "Older" cursor to use oldest item's date, not newest
- Remove redundant parent breadcrumb from all AP page headings
- Reorder tabs: Notes first, All last
- Fix avatar loading: non-destructive hide/show with lazy loading
- Add actor avatars with type badge overlay to notification cards
- Add Fediverse navigation group in sidebar
Reader now resolves ActivityPub links internally instead of navigating
to external instances. Actor links open the profile view, post links
open a new post detail view with thread context (parent chain + replies).
External links in post content get rich preview cards (title, description,
image, favicon) fetched via unfurl.js at ingest time with fire-and-forget
async processing and concurrency limiting.
New files: post-detail controller, og-unfurl module, lookup-cache,
link preview template/CSS, client-side link interception JS.
Includes SSRF protection for OG fetching and GoToSocial URL support.
- Fix Unknown authors by adding multi-strategy fallback chain in
extractObjectData (getAttributedTo → actorFallback → attributionIds)
- Fix empty boosts from Lemmy/PieFed by checking content before storing
- Fix @mention/hashtag styling to stay inline instead of breaking layout
- Fix compose reply to show sanitized HTML blockquote instead of raw text
- Add default-checked syndication targets for AP and Bluesky
- Use authenticated document loader for all lookupObject calls
(fixes 401 errors on servers requiring Authorized Fetch)
- Fix like handler 404 by using canonical AP uid for interactions
instead of display URLs; add data-item-uid to card template
- Fix profile bio showing Nunjucks macro source code by renaming
summary→bio to avoid collision with Indiekit's summary macro
- Fix Fedify API misuse in timeline-store.js: use instanceof Article
(not string comparison), replyTargetId (not inReplyTo), getTags()
and getAttachments() async methods (not sync property access)
- Fix inbox-listeners.js: use replyTargetId instead of non-existent
getInReplyTo(), use instanceof Article for Update handler
- Add error logging to interaction catch blocks
- Add actorFallback option to extractObjectData() so the activity's
actor is used when object.getAttributedTo() fails (Authorized Fetch,
unreachable servers). Falls back to attributionIds for URL-based info.
- Pass create.getActor() as actorFallback in Create inbox listener.
- Skip storing boosts with no content (Lemmy/PieFed activity IDs).
- Add template guard to hide empty cards already in the database.
- Style @mention and hashtag links distinctly from prose content.
- Handle Mastodon's invisible/ellipsis URL span classes.
Replace all nonexistent CSS variable references with Indiekit's actual
custom properties. This enables automatic dark mode support (variables
swap via prefers-color-scheme) and visual consistency with the rest of
the admin UI.
Key changes:
- Map --color-text → --color-on-background, --color-text-muted →
--color-on-offset, --border-radius → --border-radius-small, etc.
- Add post-type differentiation via colored left borders: purple for
notes, green for articles, yellow for boosts, primary for replies
- Replace hardcoded hex colors (#e11d48, #16a34a) with Indiekit's
palette variables (--color-red45, --color-green50, etc.)
- Use Indiekit's border-width tokens for consistent border sizing
- Add background/color to form inputs for dark mode compatibility
Nunjucks resolves template names across all registered plugin view
directories. Both @rmdes/indiekit-endpoint-microsub and this plugin
had views/layouts/reader.njk, causing the microsub layout to be
loaded instead — which meant Alpine.js, reader CSS, and all timeline
content were missing from the rendered page.
- Return multiple navigation items (ActivityPub, Reader, Notifications, Moderation)
so all AP sub-pages are accessible from the sidebar
- Fix Alpine.js not loading: `{% block head %}` was silently discarded because
the parent template chain has no such block — moved script/css into content block
- Pin Alpine.js to exact version 3.14.9 to prevent CDN resolution issues
- Add fallback avatar (first letter) when author photo is missing
- Guard empty author URLs to prevent broken links
- Fix Temporal.Instant TypeError: use String() instead of new Date() for
Fedify published timestamps in inbox-listeners and timeline-store
- Link author names to remote profile view instead of raw AP URLs
- Bump to 1.1.3
Add a dedicated fediverse reader view with:
- Timeline view showing posts from followed accounts with threading,
content warnings, boosts, and media display
- Compose form with dual-path posting (quick AP reply + Micropub blog post)
- Native AP interactions (like, boost, reply, follow/unfollow)
- Notifications view for likes, boosts, follows, mentions, replies
- Moderation tools (mute/block actors, keyword filters)
- Remote actor profile pages with follow state
- Automatic timeline cleanup with configurable retention
- CSRF protection, XSS prevention, input validation throughout
Removes Microsub bridge dependency — AP content now lives in its own
MongoDB collections (ap_timeline, ap_notifications, ap_interactions,
ap_muted, ap_blocked).
Bumps version to 1.1.0.
- Actor type radio buttons (Person/Service/Organization) in Profile page,
stored in ap_profile and read by federation-setup actor dispatcher
- Profile links (attachments) section with add/remove for rel="me"
verification links, rendered as PropertyValue on the ActivityPub actor
- New locale strings for all new UI elements
Inbox handlers used await activity.getObject() which HTTP-fetches remote
objects. This fails when remote servers have Authorized Fetch enabled or
are unavailable, causing Fedify to retry ~10 times per activity.
Replaced with .objectId/.actorId accessors (zero network requests) for
Like, Announce, Undo, and Delete handlers. Wrapped remaining getObject()
and getActor() calls in try-catch with fallback to ID accessors.
Also adds Pinned Posts and Featured Tags cards to the admin dashboard.
Implement all missing Fedify features for full ActivityPub compliance:
- Liked, Featured, Featured Tags collection dispatchers with admin UIs
- Object dispatcher for Note/Article dereferencing at AP URIs
- Instance actor (Application type) for domain-level federation
- Handle aliases (.mapAlias) for profile URL and /@handle resolution
- Configurable actor type (Person/Service/Organization/Group)
- Dynamic NodeInfo version from @indiekit/indiekit package.json
- Context data propagation (handle + publication URL)
- ParallelMessageQueue wrapping RedisMessageQueue (5 workers)
- Collection sync (FEP-8fcf) and ordering keys on sendActivity
- Permanent failure handler stub (deferred to Fedify 2.0)
- Profile attachments (PropertyValue) and alsoKnownAs support
- Strip invalid "type":"as:Endpoints" from actor JSON (Fedify #576)
- Fix .mapAlias() return type ({identifier} not bare string)
- Remove .authorize() predicate (causes 401 loops without auth doc loader)
- Narrow content negotiation router to /nodeinfo/ only
22/22 compliance tests pass (Grade A+). Version 1.0.26.
Replies syndicated via ActivityPub were only sent to followers.
Remote servers (e.g. Mastodon) never received the Create(Note) activity,
so replies didn't appear under the original post.
Changes:
- Resolve the reply-to post author via ctx.lookupObject() + getAttributedTo()
- Include the original author in CC addressing (ccs) on the Note
- Add a Mention tag for the original author
- Deliver the activity to the author's inbox via a second sendActivity() call
- Log reply delivery with targetUrl for debugging
Also includes: following list badge fix from refollow work, version bump to 1.0.20