From 58e3695d68567be2af82da52ef6d18568b5c2e8e Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sat, 14 Mar 2026 18:30:04 +0100 Subject: [PATCH] fix: use DOM-based dedup instead of timestamp for client-side webmentions The previous approach filtered client-side webmentions by timestamp (only show items received after buildTime). This missed webmentions that existed in the API but weren't included in the build-time cache (e.g., Bluesky interactions via Bridgy that webmention.io stored but the Eleventy cache plugin didn't fetch). Now scans the DOM for actually-rendered items: author URLs in facepiles for likes/reposts/bookmarks, and wm-url on reply cards. Only appends webmentions not already visible, regardless of when they were received. Confab-Link: http://localhost:8080/sessions/184584f4-67e1-485a-aba8-02ac34a600fe --- js/webmentions.js | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/js/webmentions.js b/js/webmentions.js index 5b630ab..61681c3 100644 --- a/js/webmentions.js +++ b/js/webmentions.js @@ -57,12 +57,40 @@ let mentionsToShow; if (hasBuildTimeSection) { - // Build-time section exists - only show NEW webmentions to avoid duplicates. - // Both webmention.io and conversations items are included at build time, - // so filter all by timestamp (only show items received after the build). - mentionsToShow = allChildren.filter((wm) => { - const wmTime = new Date(wm['wm-received']).getTime(); - return wmTime > buildTime; + // Build-time section exists — deduplicate against what's actually rendered + // in the DOM rather than using timestamps (which miss webmentions that the + // build-time cache didn't include but that the API returns). + + // Collect author URLs already shown in facepiles (likes, reposts, bookmarks) + var renderedAvatars = new Set(); + document.querySelectorAll('.webmention-likes li[data-author-url], .webmention-reposts li[data-author-url], .webmention-bookmarks li[data-author-url]').forEach(function(li) { + var authorUrl = li.dataset.authorUrl; + // Determine the type from the parent section class + var parent = li.closest('[class*="webmention-"]'); + var type = 'like-of'; + if (parent) { + if (parent.classList.contains('webmention-reposts')) type = 'repost-of'; + if (parent.classList.contains('webmention-bookmarks')) type = 'bookmark-of'; + } + if (authorUrl) renderedAvatars.add(authorUrl + '::' + type); + }); + + // Collect reply URLs already shown in reply cards + var renderedReplies = new Set(); + document.querySelectorAll('.webmention-replies li[data-wm-url]').forEach(function(li) { + if (li.dataset.wmUrl) renderedReplies.add(li.dataset.wmUrl); + }); + + mentionsToShow = allChildren.filter(function(wm) { + var prop = wm['wm-property'] || 'mention-of'; + if (prop === 'in-reply-to') { + // Skip replies whose source URL is already rendered + return !renderedReplies.has(wm.url); + } + // Skip likes/reposts/bookmarks whose author is already in a facepile + var authorUrl = (wm.author && wm.author.url) || ''; + if (authorUrl && renderedAvatars.has(authorUrl + '::' + prop)) return false; + return true; }); } else { // No build-time section - show ALL webmentions from API