Commit Graph

96 Commits

Author SHA1 Message Date
Ricardo
3bedcac4b7 chore: bump version to 2.0.31 2026-02-27 11:25:03 +01:00
Ricardo
145e329d2f feat: add TweetDeck-style deck layout for explore view
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.
2026-02-27 11:24:53 +01:00
Ricardo
525abcbf84 feat: expose signed avatar resolver for cross-plugin use
Register resolveActorAvatar() on Indiekit.config.application during
init(). Uses Fedify's authenticated document loader to fetch actor
profiles from servers with Authorized Fetch enabled (e.g., hachyderm.io,
indieweb.social). Called by the conversations plugin's avatar backfill.
2026-02-27 11:01:56 +01:00
Ricardo
cd05cb68e6 chore: bump version to 2.0.29 2026-02-27 09:27:06 +01:00
Ricardo
cee0050be8 feat: add FediDB-powered autocomplete for explore and reader lookup
- 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
2026-02-27 09:26:45 +01:00
Ricardo
27721e0969 chore: bump version to 2.0.28 2026-02-26 18:15:41 +01:00
Ricardo
a4f72a588d feat: enhance ActivityPub reader with mentions, hashtags, infinite scroll, explore, and tag following
- 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
2026-02-26 18:15:21 +01:00
Ricardo
2c4ffeaba0 fix: store actorAvatar in ap_activities for inbox interactions
Moved extractActorInfo() before logActivity() in Like, Announce, and
Reply handlers so the actor's avatar URL is persisted in ap_activities.
Previously only stored in ap_notifications, leaving conversation_items
without photos for non-followers.
2026-02-26 15:00:07 +01:00
Ricardo
ef0887e0dd docs: document Fedify workarounds and implementation notes in README
Add section covering all five workarounds carried against Fedify 2.0,
each with file location, upstream issue, and revisit conditions.
2026-02-25 09:56:32 +01:00
Ricardo
fceac1f344 feat: use authenticated document loader for all inbox handler fetches
Pass ctx.getDocumentLoader({ identifier: handle }) to every .getActor(),
.getObject(), and .getTarget() call in inbox handlers. This signs outbound
fetches with our actor's key, fixing silent failures against Authorized
Fetch (Secure Mode) servers like hachyderm.io.

The authenticated loader is also threaded through extractObjectData() and
extractActorInfo() in timeline-store.js so internal calls to
.getAttributedTo(), .getIcon(), .getTags(), and .getAttachments() also
use signed requests.

Also removes the endpoints.type workaround in federation-bridge.js since
Fedify 2.0 fixed issue #576 upstream. The attachment array workaround
for Mastodon compatibility remains.

Bumps version to 2.0.26.
2026-02-25 09:41:29 +01:00
Ricardo
17b0f582d1 feat: add remote follow / authorize interaction support
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.
2026-02-24 15:00:40 +01:00
Ricardo
a62a0d8aac fix: Fediverse field display text for Mastodon verification
Mastodon 4.5's extract_url_from_html requires element['href'] ==
element.text for a field to be verifiable. Using @handle@domain as
display text caused value_for_verification to return nil, making
the field permanently unverifiable.

Change to using the full actor URL as display text so the href and
text content match.
2026-02-24 14:05:03 +01:00
Ricardo
963dcc3014 feat: add Fediverse field to actor profile and fix Update compaction
Always include a "Fediverse" PropertyValue attachment in the actor
with the canonical @handle@domain address. This ensures 2+ attachments
when combined with user-defined fields, preventing Fedify's JSON-LD
compaction from collapsing single-element arrays to plain objects
(which Mastodon's update_account_fields silently rejects).

Also fixes the root cause of profile fields not appearing on Mastodon
for existing followers: Update(Person) activities were being sent with
compacted attachment objects that Mastodon ignored.
2026-02-24 12:07:50 +01:00
Ricardo
df5ccc397e fix: ensure attachment is always an array for Mastodon compatibility
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.
2026-02-24 11:39:42 +01:00
Ricardo
4e159bfb9d fix: replace nonexistent Nunjucks min filter with inline conditional
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).
2026-02-24 11:14:55 +01:00
Ricardo
4052eebc9d fix: re-fetch media from Fedify when stored timeline item has none
Post-detail view now re-fetches from the origin server when the stored
timeline item has empty photo/video/audio arrays (from before the async
iteration fix). On successful extraction, updates MongoDB so future
views don't need to re-fetch.
2026-02-24 10:58:13 +01:00
Ricardo
046c776195 fix: guard index creation against undefined collections on startup
MongoDB collections may not be available yet when init() runs if the
database connection hasn't completed. Wrap all createIndex calls in
try-catch so the plugin doesn't crash on startup. Indexes already exist
from previous runs; this is non-fatal.
2026-02-24 10:52:11 +01:00
Ricardo
cd7d850b44 fix: use async iteration for Fedify 2.0 attachments/tags, add image lightbox
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.
2026-02-24 10:25:15 +01:00
Ricardo
a95d68c98f fix: remove await in non-async init() causing SyntaxError on startup
dropIndex() was called with await inside the non-async init() method,
causing "Unexpected reserved word" and preventing Indiekit from starting.
Use promise .catch() instead since the result isn't needed.
2026-02-23 23:31:11 +01:00
Ricardo
18a17b054a chore: bump to 2.0.15
Fix unclosed moderation CW wrapper in ap-item-card.njk and replace
invalid Nunjucks .startsWith()/.replace() with simple field comparisons.
2026-02-23 23:17:28 +01:00
Ricardo
23fc8f4614 feat: rewrite moderation UI with filter mode, fix sparse index bug
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.
2026-02-23 23:11:28 +01:00
Ricardo
9805cb9eff fix: my-profile replies tab queries posts collection instead of ap_activities
The replies tab was empty because it queried ap_activities for outbound
Create activities with a non-null targetUrl, but targetUrl was always null
(remote actor resolution often fails). Now queries posts collection for
post-type "reply" which reliably has in-reply-to URLs.

Also fixes activity log to store in-reply-to URL as targetUrl instead of
the resolved actor URL.
2026-02-23 16:29:32 +01:00
Ricardo
743cb6b85b feat: notification tabs, my-profile page, clickable timestamps, quick-reply
- 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
2026-02-23 15:55:44 +01:00
Ricardo
376a1bb938 fix: bio formatting for remote profiles (hashtags/mentions inline)
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.
2026-02-23 13:45:44 +01:00
Ricardo
e5c0fa1191 fix: store and serve quick reply Notes for remote dereferencing
Remote servers (Mastodon, Bonfire) dereference Note IDs to verify
Create activities. Quick reply Notes had no public route — servers
got 302 to login and rejected the activity.

- Store quick reply Note data in ap_notes collection
- Add public GET /quick-replies/:id serving JSON-LD
- Use shared resolveAuthor() in compose.js for quick replies
2026-02-22 21:49:04 +01:00
Ricardo
fc63bb5b96 fix: reduce boost fetch failure logging from error to warning
Remote server timeouts during Announce.getObject() produced 20-line
stack traces. Now logs a single warning line with the cause code.
No behavior change — unreachable boosts were already skipped.
2026-02-22 21:48:23 +01:00
Ricardo
bd07edefbb fix: robust author resolution for like/boost with URL pattern fallback
When lookupObject fails (Authorized Fetch, network issues) and the post
isn't in ap_timeline, likes returned 404 "Could not resolve post author".

Adds shared resolveAuthor() with 3 strategies:
1. lookupObject on post URL → getAttributedTo
2. Timeline + notifications DB lookup
3. Extract author from URL pattern (/users/NAME/, /@NAME/)

Refactors like, unlike, boost controllers to use the shared helper.
2026-02-22 21:33:45 +01:00
Ricardo
77aad65947 fix: include reply author in cc and log delivery failures
Quick replies only sent to followers, never directly to the
replied-to author's server. The author was also missing from
the Note's cc field, so Mastodon couldn't thread or notify.

Now resolves the author before constructing the Note, includes
them in ccs, sends directly to their inbox, and logs failures
instead of silently swallowing them.
2026-02-22 21:21:55 +01:00
Ricardo
4a553da94e fix: use HTTPS activity IDs and add addressing for boost/like
Boost (Announce) was missing to/cc addressing so Mastodon silently
discarded it. Both boost and like used urn:uuid: IDs which are not
dereferenceable. Changed to HTTPS URLs and added Public/followers
addressing on Announce.
2026-02-22 20:51:18 +01:00
Ricardo
eab440bceb fix: use Fedify 2.0 replyTarget for reply threading
Fedify 2.0 renamed the Note/Article constructor parameter from
inReplyTo to replyTarget. The old name was silently ignored,
causing replies to appear as standalone posts on the fediverse.
2026-02-22 20:28:40 +01:00
Ricardo
cffe094222 fix(compose): use HTTPS Note ID and add to/cc on Create activity
Mastodon silently discards activities with urn:uuid: IDs since they
can't be dereferenced. Use an HTTPS URL under our domain instead.

Also add to/cc (Public + followers) on the Create wrapper activity,
not just the Note object — Mastodon requires addressing on both.
2026-02-22 20:13:38 +01:00
Ricardo
e0a606c8c2 fix(compose): add to/cc addressing to quick reply Notes
Remote servers (Mastodon, etc.) require explicit audience addressing
to display a post. Without to/cc, the Note was silently discarded.

- to: as:Public (visible to everyone)
- cc: followers collection
2026-02-22 19:31:45 +01:00
Ricardo
b19a33df2f fix(compose): add diagnostic logging for syndication debugging
Log syndicateTo values, micropubBody, and micropubUrl before the
Micropub request to trace why compose replies aren't syndicating
to Mastodon/Bluesky.
2026-02-22 19:18:11 +01:00
Ricardo
a6f3f8dd6c docs: update CLAUDE.md and README.md for Fedify 2.0
- Update dependencies table (remove @fedify/express, add @fedify/debugger, unfurl.js)
- Add new config options: debugDashboard, debugPassword, notificationRetentionDays
- Document new gotchas: modular imports, importSpkiPem removal, KvStore list(), debug dashboard body consumption
- Update LogTape gotcha for debug dashboard interaction
- Add debug dashboard and public profile routes to route table
- README: add public profile and debug dashboard feature sections, Fedify 2.0 mention
2026-02-22 14:36:40 +01:00
Ricardo
dd9bba711f feat: migrate to Fedify 2.0 with debug dashboard and modular imports
- Upgrade @fedify/fedify, @fedify/redis to ^2.0.0
- Add @fedify/debugger ^2.0.0 for live federation traffic dashboard
- Move all vocab type imports to @fedify/fedify/vocab (13 files)
- Move crypto imports (exportJwk, importJwk, generateCryptoKeyPair) to @fedify/fedify/sig
- Replace removed importSpki() with local Web Crypto API helper
- Add KvStore.list() async generator required by Fedify 2.0
- Add setOutboxPermanentFailureHandler for delivery failure logging
- Add debugDashboard/debugPassword config options
- Skip manual LogTape configure when debugger auto-configures it
- Fix Express-Fedify bridge to reconstruct body from req.body when
  Express body parser has already consumed the stream (fixes debug
  dashboard login TypeError)
- Add response.bodyUsed safety check in sendFedifyResponse
- Remove @fedify/express dependency (custom bridge handles sub-path mounting)
2026-02-22 14:28:31 +01:00
Ricardo
5c5e53bf3d feat: public profile page for actor URL
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.
2026-02-22 12:36:07 +01:00
Ricardo
7587d99013 feat: batch broadcast delivery and redirect browsers on actor URL
broadcastActorUpdate() now fetches followers from MongoDB, deduplicates
by shared inbox, and delivers in batches of 25 with 5s delays to prevent
thundering herd (hundreds of 499s from simultaneous re-fetches).

Browser GET on /users/:handle now redirects to homepage instead of 404.
2026-02-22 12:13:58 +01:00
Ricardo
c648606525 fix: broadcastActorUpdate was silently failing due to Fedify API mismatch
ctx.getActor() only exists on RequestContext (inside HTTP handlers), not
on the base Context returned by createContext(). Extracted actor-building
logic into shared buildPersonActor() helper used by both the dispatcher
and broadcastActorUpdate(). Profile link attachments now propagate to
remote instances via Update(Person) activity.
2026-02-22 11:27:35 +01:00
Ricardo
0fa446ceb2 feat: make Fedify log level configurable via logLevel option
Default changed from "info" to "warning" so production logs are quiet.
Set logLevel to "info" or "debug" in config to troubleshoot federation.
2026-02-21 22:51:07 +01:00
Ricardo
52088a7bce chore: bump version to 1.1.17 2026-02-21 21:33:25 +01:00
Ricardo
cf284e8633 feat: add fediverse URL/handle lookup input to reader
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.
2026-02-21 21:33:08 +01:00
Ricardo
0cf49e037c fix: remove duplicate page headings across all AP templates
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.
2026-02-21 21:32:56 +01:00
Ricardo
81373d662f chore: bump version to 1.1.16 2026-02-21 20:29:25 +01:00
Ricardo
31418310d2 fix: pagination, headers, avatars, tab order, and notification UI
- 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
2026-02-21 20:28:40 +01:00
Ricardo
937c0a8226 chore: bump version to 1.1.15 2026-02-21 20:00:20 +01:00
Ricardo
d20dea2dc8 feat: notification management — clear, mark read, dismiss, TTL retention
- Add "Mark all read" and "Clear all" toolbar buttons on notifications page
- Add per-notification dismiss (×) button
- Remove auto-mark-all-as-read on page load (explicit action only)
- Add 30-day TTL index on createdAt for automatic notification cleanup
- New config option: notificationRetentionDays (default 30)
2026-02-21 20:00:05 +01:00
Ricardo
5ff3197493 feat: add internal AP link resolution and OpenGraph card unfurling (v1.1.14)
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.
2026-02-21 18:32:12 +01:00
Ricardo
313d5d414c fix: reader UI fixes and correct Fedify API usage (v1.1.8→1.1.12)
- 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
2026-02-21 17:08:28 +01:00
Ricardo
b81ecbcaa4 docs: add CLAUDE.md for AI agents and README.md for humans
CLAUDE.md covers architecture, 18 critical gotchas distilled from
bug fixes (Fedify bridge, objectId vs getObject, template collisions,
Express 5 redirect, date handling, author fallback chain, etc.),
MongoDB collections, route table, and publishing workflow.

README.md covers features, installation, configuration, nginx setup,
how syndication/inbox/content negotiation work, Mastodon migration,
admin UI reference, and known limitations.
2026-02-21 17:06:11 +01:00
Ricardo
348a183e46 fix: profile links lost on save due to qs body parser key mismatch
express.urlencoded({ extended: true }) uses qs which strips [] from
field names, so link_name[] arrives as request.body.link_name — not
request.body["link_name[]"]. The old lookup always got undefined,
producing an empty attachments array that overwrote existing links.
2026-02-21 15:04:36 +01:00