176 Commits

Author SHA1 Message Date
Ricardo
9f1287073b feat: resolve remote profiles via WebFinger in Mastodon API
Account lookup (/api/v1/accounts/lookup) and search (/api/v2/search)
now resolve remote actors via Fedify's ctx.lookupObject() when not
found locally. Previously only checked ap_followers — missed accounts
we follow, timeline authors, and any remote actor.

Lookup chain: local profile → followers → following → timeline authors
→ remote WebFinger+actor fetch (Fedify)

Search uses remote resolution when resolve=true and query contains @.
2026-03-21 11:49:12 +01:00
Ricardo
01edd6e92e fix: improve timeline content for own posts (4 issues)
1. Empty content on bookmarks/likes/reposts: synthesize content from
   the interaction target URL (bookmark-of, like-of, repost-of) when
   the post has no body text

2. Hashtags not extracted: parse #hashtag patterns from content text
   and merge with explicit categories. Applies to both backfill
   (startup) and POST /api/v1/statuses (runtime)

3. Hashtag links rewritten: /categories/tag/ links (site-internal)
   are rewritten to /tags/tag (Mastodon convention) in the HTML
   content stored in ap_timeline

4. Relative media URLs resolved: photo/video/audio URLs like
   media/photos/... are resolved to absolute URLs using the site URL
2026-03-21 10:34:11 +01:00
Ricardo
2a4ac75c77 fix: use HTML+JS redirect for native app OAuth callbacks
Android Chrome Custom Tabs block 302 redirects to custom URI schemes
(fedilab://, moshidon-android-auth://) for security. The server sends
the redirect correctly but the WebView silently ignores it — "nothing
happens" when the user taps Authorize.

Fix: detect non-HTTP redirect URIs and render an HTML page with both
a JavaScript window.location redirect and a meta refresh fallback.
Client-side navigation to custom schemes is allowed by WebViews.

HTTP(S) redirect URIs (Phanpy, Elk) still use standard 302.
2026-03-21 09:42:31 +01:00
svemagie
ce30dfea3b feat(activitypub): AP protocol compliance — Like id, Like dispatcher, repost commentary, ap-url API
Five improvements to strict ActivityPub protocol compliance and
real-world Mastodon interoperability:

1. allowPrivateAddress: true in createFederation (federation-setup.js)
   Fixes Fedify's SSRF guard rejecting own-site URLs that resolve to
   private IPs on the local LAN (e.g. home-network deployments where
   the blog hostname maps to 10.x.x.x internally).

2. Canonical id on Like activities (jf2-to-as2.js)
   Per AP §6.2.1, activities SHOULD have an id URI so remote servers
   can dereference them. Derives mount path from actor URL and constructs
   {publicationUrl}{mount}/activities/like/{post-path}.

3. Like activity object dispatcher (federation-setup.js)
   Per AP §3.1, objects with an id MUST be dereferenceable at that URI.
   Registers federation.setObjectDispatcher(Like, .../activities/like/{+id})
   so fetching the canonical Like URL returns the activity as AP JSON.
   Adds Like to @fedify/fedify/vocab imports.

4. Repost commentary in AP output (jf2-to-as2.js)
   - jf2ToAS2Activity: only sends Announce for pure reposts (no content);
     reposts with commentary fall through to Create(Note) with content
     formatted as "{commentary}<br><br>🔁 <url>" so followers see the text.
   - jf2ToActivityStreams: prepends commentary to the repost Note content
     for correct display in content-negotiation / search responses.

5. GET /api/ap-url public endpoint (index.js)
   Resolves a blog post URL → its Fedify-served AP object URL for use by
   "Also on Fediverse" widgets. Prevents nginx from intercepting
   authorize_interaction requests that need AP JSON.
   Special case: AP-likes return { apUrl: likeOf } so authorize_interaction
   opens the original remote post rather than the blog's like post.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:12:21 +01:00
Ricardo
41c43be4cb fix: rename variable to avoid 'published' redeclaration (SyntaxError) 2026-03-20 20:36:51 +01:00
Ricardo
c0d4b77b94 fix: sort Mastodon API timeline by published date instead of ObjectId
The Mastodon API timeline sorted by MongoDB _id (insertion order), not
by published date. This caused chronological jumps — backfilled or
syndicated posts got ObjectIds at import time, interleaving them
incorrectly with federation-received posts.

Changes:
- Pagination cursors now use published date (encoded as ms-since-epoch)
  instead of ObjectId. Mastodon clients pass these as opaque max_id/
  min_id/since_id values and they sort correctly.
- Status and notification IDs are now encodeCursor(published) so the
  cursor round-trips through client pagination.
- Status lookups (GET/DELETE /statuses/:id, context, interactions) use
  findTimelineItemById() which tries published-based lookup first, then
  falls back to ObjectId for backwards compatibility.
- Link pagination headers emit published-based cursors.

This matches the native reader's sort (storage/timeline.js) which has
always sorted by published: -1.
2026-03-20 18:05:45 +01:00
Ricardo
a8947b205f fix: omit null fields instead of setting them in OAuth token documents
MongoDB sparse indexes skip documents where the indexed field is ABSENT,
but still enforce uniqueness on explicit null values. The auth code insert
set accessToken:null and the client_credentials insert set code:null,
causing E11000 duplicate key errors on the second authorization attempt.

Fix: omit accessToken/code entirely from inserts where they don't apply.
The field gets added later during token exchange ($set in updateOne).
2026-03-20 17:25:25 +01:00
Ricardo
f55cfbfcd2 fix: use existing default-avatar.svg instead of missing placeholder-avatar.png
The fallback avatar URL pointed to /placeholder-avatar.png which doesn't
exist (404). Changed to /images/default-avatar.svg which exists in the
Eleventy theme and is served by the nginx image caching location with
CORS headers — fixing cross-origin errors in Phanpy/Elk.
2026-03-20 15:30:50 +01:00
Ricardo
0cde298b46 fix: detect own posts in Mastodon API status serialization
Own posts in ap_timeline have author.url set to the publication URL
(site root like "https://rmendes.net/") with no /@handle or /users/handle
pattern. extractUsername("/") returns "" which falls back to "unknown".

Fix: set module-level local identity (publicationUrl + handle) at plugin
init via setLocalIdentity(). serializeStatus() compares item.author.url
against the publication URL and passes isLocal:true + handle to
serializeAccount() when they match.

This is zero-cost for callers — no signature changes needed at the 20+
serializeStatus() call sites.
2026-03-20 14:00:44 +01:00
svemagie
842fc5af2a feat(like): send Like activity for AP objects, bookmark for regular URLs
When the `like-of` URL serves ActivityPub content (detected via content
negotiation with Accept: application/activity+json), deliver a proper
`Like { actor, object, to: Public }` activity to followers.

For likes of regular (non-AP) URLs, fall through to the existing
bookmark-style `Create(Note)` behaviour (🔖 content with #bookmark tag).

- Add `isApUrl()` async helper (3 s timeout, fails silently)
- Make `jf2ToAS2Activity` async; add Like detection before repost block
- Update all four call sites in federation-setup.js and index.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 08:50:00 +01:00
svemagie
03c4ba4aea fix(og-image): use slug-only path for OG image URL
OG images are served at /og/{slug}.png (e.g. /og/14b61.png),
not with date prefixes. Remove the date segments from the URL
construction in both jf2ToActivityStreams and jf2ToAS2Activity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 06:37:39 +01:00
svemagie
45f8ba93c0 feat: deliver likes as bookmarks, revert announce cc, add OG images
- Likes are now sent as Create/Note with bookmark-style content (🔖)
  instead of Like activities, ensuring proper display on Mastodon
- Announce activities reverted to upstream addressing (to: Public only,
  no cc:followers)
- Add per-post OG image to both plain JSON-LD and Fedify Note/Article
  objects, derived from the post URL pattern (/og/{date}-{slug}.png)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 01:34:59 +01:00
svemagie
b99f5fb73e Merge upstream rmdes:main — v2.13.0–v2.15.4 into svemagie/main
New upstream features:
- v2.13.0: FEP-8fcf/fe34 compliance, custom emoji, manual follow approval
- v2.14.0: Server blocking, Redis caching, key refresh, async inbox queue
- v2.15.0: Outbox failure handling (strike system), reply chain forwarding
- v2.15.1: Reply intelligence in reader (visibility badges, thread reconstruction)
- v2.15.2: Strip invalid as:Endpoints type from actor serialization
- v2.15.3: Exclude soft-deleted posts from outbox/content negotiation
- v2.15.4: Wire content-warning property for CW text

Conflict resolution:
- federation-setup.js: merged our draft/unlisted/visibility filters with
  upstream's soft-delete filter
- compose.js: kept our DM compose path, adopted upstream's
  lookupWithSecurity for remote object resolution
- notifications.js: kept our separate reply/mention tabs, added upstream's
  follow_request grouping
- inbox-listeners.js: took upstream's thin-shim rewrite (handlers moved to
  inbox-handlers.js which already has DM detection)
- notification-card.njk: merged DM badge with follow_request support

Preserved from our fork:
- Like/Announce to:Public cc:followers addressing
- Nested tag normalization (cat.split("/").at(-1))
- DM compose/reply path in compose controller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 00:42:31 +01:00
svemagie
d143abf392 fix: add to/cc addressing to Like and Announce activities
Mastodon shared inboxes require cc:followers to route activities to
local followers. Like had no to/cc at all, Announce was missing cc.
Also normalize nested tags (on/art/music → music) in hashtag names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 00:16:18 +01:00
Ricardo
2c0cfffd54 feat: add Mastodon Client API layer for Phanpy/Elk compatibility
Implement the Mastodon Client REST API (/api/v1/*, /api/v2/*) and OAuth2
server within the ActivityPub plugin, enabling Mastodon-compatible clients
to connect to the Fedify-based server.

Core features:
- OAuth2 with PKCE (S256) — app registration, authorization, token exchange
- Instance info + nodeinfo for client discovery
- Account lookup, verification, relationships, follow/unfollow/mute/block
- Home/public/hashtag timelines with cursor-based pagination
- Status viewing, creation, deletion, thread context
- Favourite, boost, bookmark interactions with AP federation
- Notifications with type filtering and pagination
- Search across accounts, statuses, and hashtags
- Markers for read position tracking
- Bookmarks and favourites collection lists
- 25+ stub endpoints preventing client errors on unimplemented features

Architecture:
- 24 new files under lib/mastodon/ (entities, helpers, middleware, routes)
- Virtual endpoint at "/" via Indiekit.addEndpoint() for domain-root access
- CORS + JSON error handling for browser-based clients
- Six-layer mute/block filtering reusing existing moderation infrastructure

BREAKING CHANGE: bumps to v3.0.0 — adds new MongoDB collections
(ap_oauth_apps, ap_oauth_tokens, ap_markers) and new route registrations

Confab-Link: http://localhost:8080/sessions/5360e3f5-b3cc-4bf3-8c31-5448e2b23947
2026-03-18 12:50:52 +01:00
Ricardo
2ca491f28b fix: wire content-warning property for CW text
Updated jf2-to-as2 and compose controller to use the renamed
"content-warning" property instead of overloading "summary" for
CW text. This pairs with the endpoint-posts fix that renamed the
CW form input to prevent collision with the summary field.

Confab-Link: http://localhost:8080/sessions/1dcdf030-8015-4d23-89da-b43fd69c7138
2026-03-18 00:24:35 +01:00
Ricardo
26c81a6a76 fix: exclude soft-deleted posts from outbox and content negotiation
Deleted posts (with properties.deleted timestamp) were still served
via the outbox dispatcher and content negotiation catch-all. Now:
- Outbox find() and countDocuments() filter out deleted posts
- Object dispatcher returns null for deleted posts (Fedify 404)
- Content negotiation falls through to Express for deleted posts

Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
2026-03-17 17:12:30 +01:00
Ricardo
4f0d7925b2 fix: strip invalid as:Endpoints type from Fedify actor serialization
Fedify serializes the endpoints object with "type": "as:Endpoints" which
is not a valid ActivityStreams 2.0 type. This causes browser.pub validation
failures. Strip the type field in the JSON patching block.

Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
2026-03-17 15:07:46 +01:00
Ricardo
c8aa0383b9 feat: wire reply intelligence to frontend — timeline filtering, thread reconstruction, visibility badges
- Filter isContext items and private/direct posts from main timeline, new post count, and unread count
- Post detail: query local replies from ap_timeline before remote fetch, deduplicate, sort chronologically
- Add visibility badge (unlisted/private/direct) on item cards next to timestamp

Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
2026-03-17 13:13:51 +01:00
Ricardo
206ae4c6e5 feat: Hollo-inspired federation patterns — outbox failure handling, reply chains, forwarding, visibility
- Add outbox permanent failure handling with smart cleanup:
  - 410 Gone: immediate full cleanup (follower + timeline + notifications)
  - 404: strike system (3 failures over 7+ days triggers cleanup)
  - Strike reset on inbound activity (proves actor is alive)
- Add recursive reply chain fetching (depth 5) with isContext flag
- Add reply forwarding to followers for public replies to our posts
- Add write-time visibility classification (public/unlisted/private/direct)

Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
2026-03-17 11:11:18 +01:00
Ricardo
1567b7c4e5 feat: operational resilience hardening — server blocking, caching, key refresh, async inbox (v2.14.0)
- Server-level blocking: O(1) Redis SISMEMBER check in all inbox listeners,
  admin UI for blocking/unblocking servers by hostname, MongoDB fallback
- Redis caching for collection dispatchers: 300s TTL on followers/following/liked
  counters and paginated pages, one-shot followers recipients cache
- Proactive key refresh: daily cron re-fetches actor documents for followers
  with 7+ day stale keys using lookupWithSecurity()
- Async inbox processing: MongoDB-backed queue with 3s polling, retry (3 attempts),
  24h TTL auto-prune. Follow keeps synchronous Accept, Block keeps synchronous
  follower removal. All other activity types fully deferred to background processor.

Inspired by wafrn's battle-tested multi-user AP implementation.

Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
2026-03-17 09:16:05 +01:00
Ricardo
9a61145d97 feat: FEP-8fcf/fe34 compliance, custom emoji, manual follow approval (v2.13.0)
- FEP-8fcf: add syncCollection to Undo(Announce) sendActivity
- FEP-fe34: centralized lookupWithSecurity() helper with crossOrigin: "ignore" on all 23 lookupObject call sites
- Custom emoji: replaceCustomEmoji() renders :shortcode: as inline <img> in content and actor display names
- Manual follow approval: profile toggle, ap_pending_follows collection, approve/reject controllers with federation, pending tab on followers page, follow_request notification type
- Coverage audit updated to v2.12.x (overall ~70% → ~82%)

Confab-Link: http://localhost:8080/sessions/1f1e729b-0087-499e-a991-f36f46211fe4
2026-03-17 08:21:36 +01:00
svemagie
8b9bff4d2e fix: AP inbox reliability — PeerTube View skip, raw body digest, signature window, trailing slash
federation-bridge.js:
- Buffer application/activity+json and ld+json bodies that Express
  doesn't parse (inbox POSTs from Mastodon, PeerTube, etc.)
- Store original bytes in req._rawBody and pass them verbatim to Fedify
  so HTTP Signature Digest verification passes; JSON.stringify reorders
  keys which caused every Mastodon Like/Announce/Create to be silently
  rejected
- Short-circuit PeerTube View (WatchAction) activities with 200 before
  Fedify's JSON-LD parser throws on Schema.org extensions

federation-setup.js:
- Accept signatures up to 12 hours old (Mastodon retries with the
  original signature hours after a failed delivery)
- Look up AP object URLs with $in [url, url+"/"] to tolerate trailing
  slash differences between stored posts and AP object URLs

inbox-listeners.js:
- Register a no-op .on(View) handler so Fedify doesn't log noisy
  "Unsupported activity type" errors for PeerTube watch events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:13:47 +01:00
svemagie
d0cb9a76aa Merge upstream rmdes:main — v2.11.0, v2.12.0, v2.12.1 into svemagie/main (v2.12.2)
Integrates upstream features (visibility/CW compose controls, @mention
support, federation management page, layout fix) while preserving
svemagie DM support. Visibility and syndication controls are hidden
for direct messages.
2026-03-15 19:25:54 +01:00
Ricardo
19aa83ab8d feat: federation management page with collection stats, post actions, object lookup (v2.12.0)
Confab-Link: http://localhost:8080/sessions/c2335791-4b8c-44a6-b1b7-8d0fa8d7f647
2026-03-15 16:32:14 +01:00
Ricardo
6238e7d4e5 feat: visibility/CW compose controls, @mention support (v2.11.0)
Add visibility and content warning controls to the reply compose form.
Add @user@domain mention parsing, WebFinger resolution, Mention tags,
inbox delivery, and content linkification for outbound posts.

Confab-Link: http://localhost:8080/sessions/cc343b15-8d10-43cd-a48f-ca912eb79b83
2026-03-14 21:28:24 +01:00
Sven Giersig
eefa46f0c1 Merge upstream rmdes:main — v2.10.0 (Delete, visibility, CW, polls, Flag) into svemagie/main (v2.10.1)
Upstream v2.10.0 adds: outbound Delete, visibility addressing (unlisted/
followers-only), Content Warning (sensitive flag + summary), inbound poll
rendering, Flag/report handler, DM support files.

Conflict resolution — all four conflicts were additive (no code removed):

  lib/controllers/reader.js: union of validTabs — fork added "mention",
    upstream added "dm" and "report"; result keeps all five additions.

  lib/storage/notifications.js: union of count keys — fork added mention:0,
    upstream added dm:0 and report:0; result keeps the fork's mention split
    logic alongside the new upstream keys.

  views/partials/ap-notification-card.njk: fork kept isDirect 🔒 badge for
    direct mentions; upstream added ✉ for dm and ⚑ for report; result keeps
    the isDirect branch and appends the two new type badges.

  package.json: upstream bumped to 2.10.0; we bump to 2.10.1 to reflect our
    own Alpine.js and publication-aware docloader bug fixes on top.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 13:00:58 +01:00
Sven Giersig
b8e0beb5a3 fix: load Alpine.js in reader layout and allow private addresses for own host in author resolution (v2.9.2)
- views/layouts/ap-reader.njk: Replace incorrect comment "Alpine.js
  loaded by default.njk" with an actual Alpine.js CDN script tag.
  Without this, all Alpine directives on the remote-profile page
  (x-data, @click, x-text, :class) were dead — Follow/Mute/Block
  buttons showed no label and clicks did nothing.

- lib/resolve-author.js: Add createPublicationAwareDocumentLoader()
  which wraps the authenticated Fedify document loader to opt in to
  allowPrivateAddress for requests to the publication's own hostname.
  Fedify blocks private IP ranges by default; self-hosted instances
  (localhost / private IPs) were failing author resolution for their
  own posts with a private-address error. All three lookupObject calls
  in resolveAuthor() now use the wrapped loader.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:57:30 +01:00
Ricardo
1dc42ad5e5 feat: outbound Delete, visibility addressing, CW/sensitive, polls, Flag reports (v2.10.0)
- 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
2026-03-14 08:51:44 +01:00
svemagie
0acd324070 fix: normalize aliasUrl to absolute URL before storing alsoKnownAs
Without https://, jsonld rejects the value as a relative object
reference during signature verification, breaking Mastodon migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:24:41 +01:00
svemagie
a5223437b8 feat: add getDirectConversations to notifications storage
Groups ap_notifications with isDirect:true by peer actor and returns
them as conversation objects shaped for the DM reader tab template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:12:54 +01:00
svemagie
39d45ec04e feat: integrate docloader loglevel, unlisted guards, alias-clear, likePost/boostPost
federation-setup.js:
- Suppress fedify docloader logs below fatal level to reduce noise from
  deleted remote actors (404/410)
- Add visibility:unlisted guard to outbox dispatcher, counter, and
  resolvePost object dispatcher

controllers/migrate.js:
- Allow clearing alsoKnownAs by detecting submitted empty aliasUrl field
  via hasOwnProperty check (previously only set when non-empty)

index.js:
- Add resolveAuthor import
- Skip federation for unlisted posts in syndicate()
- Add likePost(postUrl, collections) — sends AP Like activity to author
- Add boostPost(postUrl, collections) — sends AP Announce to followers
  and directly to the post author's inbox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:08:23 +01:00
svemagie
445aab5632 fix: serve like/repost posts as Note for AP content negotiation
Returning a bare Like/Announce activity breaks Mastodon's
authorize_interaction flow because it expects a content object
(Note/Article). Serve as Note with emoji + linked URL instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:49:50 +01:00
svemagie
364c41cba7 feat: add Direct Messages tab to reader view 2026-03-13 07:36:09 +01:00
svemagie
51a8abb0a2 feat: use getDirectConversations() for mention tab; pass conversations to template 2026-03-13 07:26:49 +01:00
svemagie
ba144da14c feat: store outbound DMs in ap_notifications for threading; redirect to ?tab=mention 2026-03-13 07:26:25 +01:00
svemagie
8d2dc3a05a fix: use ctx.lookupObject for DM recipient instead of resolveAuthor (actor URL, not post URL) 2026-03-13 07:12:19 +01:00
svemagie
4b4faea52f fix: use getTags() async iterator and instanceof checks for Mention/Hashtag 2026-03-13 06:59:27 +01:00
svemagie
ea9a9856e9 feat: direct message (DM) receive and reply support
- Detect incoming DM visibility in inbox listener by checking absence of
  the public collection URL in object.toIds/ccIds; store isDirect and
  senderActorUrl on mention notifications
- Add native AP reply path in compose controller: when is-direct=true,
  build Create(Note) addressed only to the sender and deliver via
  ctx.sendActivity() instead of posting a public Micropub blog reply
- Add dedicated "Direct" tab to notifications view (separate from Replies)
  with its own count; update storage query so mention tab filters only
  mention type, reply tab filters only reply type
- Show lock badge (🔒) on direct mention notification cards and add
  ap-notification--direct CSS class
- Compose view: show DM notice banner, hide syndication targets, and
  change submit label when replying to a direct message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 06:32:50 +01:00
Ricardo
1c2fb321bc feat: image rendering, link preview CSS, lightbox swipe, URL linkification (v2.8.0)
- 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
2026-03-06 10:42:39 +01:00
Ricardo
2083741535 fix: use human-readable URLs for reply-to links (v2.7.1)
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
2026-03-05 08:24:08 +01:00
Ricardo
7611dba40f feat: remove quick reply, streamline blog reply (v2.7.0)
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
2026-03-04 17:33:02 +01:00
Ricardo
b9fc98f40c feat: content enhancements — URL shortening, hashtag collapse, bot badge, edit indicator (Release 7)
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
2026-03-03 16:40:01 +01:00
Ricardo
2d2dcaec7d feat: interaction counts on timeline cards (Release 5)
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
2026-03-03 14:30:40 +01:00
Ricardo
c243b70629 feat: enriched media model with ALT badges (Release 3+4)
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
2026-03-03 13:46:58 +01:00
Ricardo
02d449d03c feat: render custom emoji in reader (Release 1)
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
2026-03-03 13:13:28 +01:00
Ricardo
af2f899073 refactor: unify reader and explore processing pipeline (Release 0)
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
2026-03-03 12:48:40 +01:00
Ricardo
508ac75363 feat: new posts banner, mark-as-read on scroll, unread filter
- 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
2026-03-02 10:54:11 +01:00
Ricardo
120f2ee00e feat: render quoted posts as embedded cards in reader
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
2026-03-02 10:33:11 +01:00
Ricardo
abf1b94bd6 feat: migrate Fedify KV store and plugin cache from MongoDB to Redis
Replace unbounded ap_kv MongoDB collection (169K docs, 49MB) with Redis:
- Fedify KV store uses @fedify/redis RedisKvStore (native TTL support)
- Plugin cache (fedidb, batch-refollow state, migration flags) uses new
  redis-cache.js utility with indiekit: key prefix
- All controllers updated to remove kvCollection parameter passing
- Addresses OOM kills caused by ap_kv growing ~14K entries/day
2026-03-01 16:26:17 +01:00