Commit Graph

220 Commits

Author SHA1 Message Date
Ricardo
520bc5f582 debug: inline ogSlug computation + debug comment to diagnose race condition
Computes ogSlug using inline Nunjucks string ops instead of filter call.
Adds HTML debug comment showing page.url, permalink, and computed ogSlug
to diagnose Eleventy 3.x parallel rendering race condition.
2026-02-24 19:39:59 +01:00
Ricardo
ad8af6f027 fix: use page.url instead of permalink for ogSlug in templates
permalink is set by eleventyComputed which cross-contaminates return
values across pages during Eleventy 3.x parallel rendering. page.url
is set by Eleventy's internal pipeline and is correct in templates
(verified via og:url meta tag which always shows the right URL).
2026-02-24 18:54:39 +01:00
Ricardo
7110ba3879 fix: compute ogSlug from permalink in template to avoid Eleventy 3.x race condition
Both page.url AND page.inputPath are unreliable in eleventyComputed due to
Eleventy 3.x parallel rendering (issue #3183). They return values from OTHER
pages being processed concurrently, causing og:image meta tags to reference
wrong OG images.

Fix: compute ogSlug directly in base.njk from the permalink data value using
existing Nunjucks filters (ogSlug, hasOgImage). permalink comes from frontmatter
(per-file data) and is immune to cross-page contamination.
2026-02-24 18:33:46 +01:00
Ricardo
a7e68ee941 fix: derive ogSlug from inputPath to fix Eleventy 3.x race condition
page.url in eleventyComputed returns URLs from other pages being
processed concurrently in Eleventy 3.x parallel rendering. This caused
OG images to show wrong post types and titles (e.g., a note showing
"Reply" badge from a completely different post).

Fix: use page.inputPath (physical file path) which is always correct,
matching the approach already used by the permalink computation.
2026-02-24 17:55:31 +01:00
Ricardo
167b6620cc fix: use side-specific border color on post-list items
The .post-list li rule used border-surface-200 which sets border-color
for ALL sides (shorthand). Combined with its higher specificity (0-1-1
vs 0-1-0), this overrode the border-l-{color} utility classes on
.post-card elements. Changing to border-b-surface-200 restricts the
color to only the bottom separator border, allowing the left border
color utilities to apply correctly.
2026-02-24 17:21:02 +01:00
Ricardo
91ed859a35 feat: harmonize blog views with homepage UI/UX
Add color-coded left borders to post cards on all blog listing and
category pages, and make sidebar widgets collapsible with localStorage
persistence on both listing and single-post sidebars.
2026-02-24 16:58:46 +01:00
Ricardo
b8c76e8f22 fix: recent-posts widget fallback to collections.recentPosts
The widget used bare 'recentPosts' variable which doesn't exist in
the homepage context (it's a collection). Add fallback to
collections.recentPosts so the widget works on all pages.
2026-02-24 16:23:42 +01:00
Ricardo
910889cde8 fix: remove trailing slash from site.url to prevent double-slash URLs
site.url had a trailing slash (added for Mastodon rel=me verification),
which caused double slashes in all URL constructions like
{{ site.url }}/auth → https://rmendes.net//auth

This broke IndieAuth login — indielogin.com read the authorization_endpoint
link tag with //auth and redirected users there, which 404'd in nginx.

Split into site.url (no slash, for URL construction) and site.me /
site.author.url (with slash, for Mastodon rel=me strict matching).

Also fixed twitter:image meta tags to use smart slash logic matching
the og:image pattern (check if path starts with / before prepending one).
2026-02-24 16:18:52 +01:00
Ricardo
20f7823ee1 fix: apply accordion to personal/work project variants
The homepage uses cv-projects-personal.njk, not cv-projects.njk.
Apply the same collapsible accordion pattern to both personal
and work project section variants.
2026-02-24 16:07:18 +01:00
Ricardo
b8b1ebcdf4 feat: add collapsible sidebar widgets with localStorage persistence
Sidebar widgets are now wrapped in a collapsible Alpine.js container
with a title + chevron toggle. First 3 widgets open by default, rest
collapsed. State persists in localStorage across page loads. Inner
widget titles hidden by CSS to avoid duplication with wrapper titles.
2026-02-24 15:40:00 +01:00
Ricardo
c66a46e349 feat: convert projects section to collapsible accordion
Project cards now show a compact summary row (name, status badge, date
range, chevron) that expands on click to reveal description and tech
tags. Uses Alpine.js with independent toggles and smooth transitions.
2026-02-24 15:37:19 +01:00
Ricardo
0279cfa977 feat: add color-coded left borders to post cards by type
Each post card in the recent-posts section now has a 3px left border
colored by post type: red for likes, amber for bookmarks, green for
reposts, blue for replies, purple for photos, neutral for articles/notes.
2026-02-24 15:35:22 +01:00
Ricardo
f5f77cb4a3 fix: normalize site URL with trailing slash for Mastodon rel=me verification
Mastodon's VerifyLinkService uses strict string comparison against
account.url (which includes trailing slash from the AP actor's url
field). The h-card self-link used SITE_URL without trailing slash,
causing the comparison to fail silently.
2026-02-24 13:56:29 +01:00
Ricardo
90ff9068a1 feat: use AP handle as primary fediverse identity
Rename getMastodonHandle() to getFediverseCreator() and prefer the
site's own ActivityPub handle (ACTIVITYPUB_HANDLE) over the external
Mastodon account for the fediverse:creator meta tag. The Mastodon
account is a syndication target, not the canonical identity.
2026-02-24 12:08:02 +01:00
Ricardo
2c172566c8 feat: add fediverse follow me sidebar widget
Reuses the existing fediverseInteract Alpine.js component to let
visitors follow the site author from their own fediverse instance.
Registered in all three sidebar routers (homepage, blog listing,
blog post) as widget type "fediverse-follow".
2026-02-23 16:50:27 +01:00
Ricardo
c7f2841625 fix: auto-convert stale /content/ permalinks in data cascade
Posts published between Jan 19 – Feb 6 2026 (pre-beta.37 preset) had
permalink: /content/TYPE/YYYY-MM-DD-SLUG/ in frontmatter. The data
cascade trusted these values, causing Eleventy to generate pages at
/content/ paths instead of canonical URLs. This left 86 posts (including
articles like collecteur-de-flux-rss) returning 404 at their canonical
URLs.

The markdown files were fixed on the server (permalink lines removed),
but this adds a safety net: any remaining or future /content/ permalinks
are auto-converted to /TYPE/YYYY/MM/DD/SLUG/ format.
2026-02-23 12:31:44 +01:00
Ricardo
e3f81293d1 fix: align client-side webmention selectors with build-time HTML
- Change .avatar-row selector to .facepile to match build-time template
- Use facepile-avatar class for dynamically created avatar links
- Fix pluralization in updateCount (was only replacing the number,
  now rebuilds the full "N Like/Likes" text correctly)
- Align ring color classes with build-time template
2026-02-23 11:02:32 +01:00
Ricardo
f898837b25 fix: remove platform exception in client-side webmention filter
Conversations items are now included in build-time rendering via
conversationMentions data, so they no longer need a special exception
to bypass the timestamp filter. All items (webmention.io and
conversations) are now filtered equally by build timestamp.
2026-02-23 10:58:48 +01:00
Ricardo
1bc6aaa0a5 fix: add author-based dedup to client-side webmentions
The client-side webmentions.js was deduplicating by wm-id and source
URL, but conversations API and webmention.io use different ID formats
(string vs numeric). Add author URL + action type dedup to catch
cross-source duplicates (e.g., same Bluesky like reported by both).
2026-02-23 10:40:14 +01:00
Ricardo
a7bc472e87 fix: deduplicate cross-source webmentions
webmention.io cache and conversations API can report the same
interaction (e.g. a like) with different wm-id formats. Deduplicate
by author URL + interaction type after URL filtering to prevent
the same like/reply appearing twice.
2026-02-23 10:06:19 +01:00
Ricardo
e1f4b2cd90 feat: eliminate URL dualism — computed permalinks, conversations support
- Add computed permalink in data cascade for existing posts without
  frontmatter permalink (converts file path to Indiekit URL)
- Fix ogSlug filter and computed data for new 5-segment URL structure
- Add conversations API as build-time data source
- Merge conversations + webmentions in webmentionsForUrl filter with
  deduplication and legacy /content/ URL alias computation
- Sidebar widget fetches from both webmention-io and conversations APIs
- Update webmention-debug page with conversationMentions parameter
2026-02-23 09:38:30 +01:00
Ricardo
c457eb6f04 fix: extractFirstImage matching x-bind:src from Alpine.js
The regex matched x-bind:src="comment.author.photo" from the comments
component, causing the literal string to appear in og:image meta tags.
Every Mastodon instance fetching OG data hit /comment.author.photo → 404.

Require whitespace before src= so only actual HTML src attributes match.
2026-02-22 20:37:07 +01:00
Ricardo
834fd2b927 fix: use AP object URL for fediverse authorize_interaction
Pass the self-hosted ActivityPub syndication URL to the modal instead
of the Eleventy HTML page URL. Mastodon's authorize_interaction needs
a resolvable AP URI, not the HTML page which lacks an activity+json
alternate link.
2026-02-22 16:32:10 +01:00
Ricardo
b4e0b3f841 fix: modal click.outside race condition closing modal immediately
Replace @click.outside on modal panel with @click.stop — the backdrop
already handles closing. @click.outside fires from the same click event
that opens the modal via x-if, immediately setting showModal back to false.
2026-02-22 16:29:23 +01:00
Ricardo
5d8222e5ae fix: vendor Alpine.js and lite-youtube-embed locally instead of CDN
jsdelivr CDN outages (503) break Alpine.js loading, making all
interactive components non-functional. Bundle vendor JS/CSS locally
to eliminate external CDN dependency.
2026-02-22 16:24:43 +01:00
Ricardo
0962e054d1 fix: remove fediverse node autocomplete (CORS blocked)
nodes.fediverse.party doesn't send CORS headers, so the fetch fails
from the browser. Remove autocomplete entirely — users type their
instance once and localStorage remembers it.
2026-02-22 16:08:20 +01:00
Ricardo
8597856589 feat: add fediverse remote interaction button and syndication platform buttons
Add a Fediverse button to the "Also on" footer for posts syndicated via
self-hosted ActivityPub. Clicking it redirects users to their own instance
via authorize_interaction so they can like/boost/reply natively. Instance
is stored in localStorage for repeat visits, with a modal for first-time
entry and Shift+click to change.

Also adds branded syndication buttons for LinkedIn and IndieNews, and
replaces the heuristic Mastodon URL detection with exact matching against
the configured MASTODON_INSTANCE.
2026-02-22 15:56:01 +01:00
Ricardo
c70b51b499 feat: add ActivityPub actor rel="me" link for Mastodon verification
Add ACTIVITYPUB_HANDLE env var support to site.js social links.
Add activitypub globe icon to h-card sidebar widget.
2026-02-22 12:14:06 +01:00
Ricardo
f8aae34c01 fix: exclude draft posts from all Eleventy collections
Posts with `draft: true` frontmatter were included in every collection
(posts, notes, articles, feed, recentPosts, categories, etc.), making
them visible on the blog, homepage, RSS feed, and sidebar. Added an
isPublished filter to all 12 collections.
2026-02-22 11:04:49 +01:00
Ricardo
4fc5701290 fix: use one-shot flag for pagefind instead of incremental guard
The --incremental CLI flag sets incremental=true in eleventy.after even
for the watcher's first full build, so pagefind was never running when
the initial build was OOM-killed. Replace the incremental guard with a
pagefindDone boolean that runs pagefind exactly once per process lifetime
— whichever build completes first (initial or watcher) gets indexed.
2026-02-22 01:07:52 +01:00
Ricardo
7c64b2c4ce fix: restore pagefind to eleventy.after hook
Pagefind indexing was moved to start.sh in f2cc855 but the shell-based
approach is fragile: the recovery mechanism timed out when the initial
build was OOM-killed, leaving no search index. The eleventy.after hook
is the natural place — Eleventy knows the correct output directory and
the incremental guard prevents re-indexing on watch rebuilds.

Timeout increased to 120s for the larger site (2194 pages).
2026-02-22 00:58:53 +01:00
Ricardo
1bf6f9358a feat: wrap all widgets and comments in is-land on:visible
Add <is-land on:visible> lazy-loading wrapper to every widget template
and the comments section for consistent deferred rendering. Widgets
that already had it (social-activity, github-repos, blogroll, feedland,
webmentions) are unchanged. Also wraps inline search and custom-html
widgets in all sidebar container files.
2026-02-22 00:31:01 +01:00
Ricardo
791dccf223 fix: make is-land elements display block for proper widget spacing
The <is-land> custom element defaults to display:inline, which breaks
margin spacing between adjacent widgets. Setting it to block ensures
widgets wrapped in is-land (blogroll, social-activity, github, etc.)
get proper mb-4 spacing from the .widget class.
2026-02-22 00:24:36 +01:00
Ricardo
74933687c1 feat: unify listening widget with Funkwhale + Last.fm sources
Show 2 recent tracks from each source (4 total) instead of only
Funkwhale. Removed stats section — users can visit /listening/ for
full statistics. Now Playing indicator works from either source.
2026-02-22 00:13:41 +01:00
Ricardo
557d9f18a0 fix: use .widget class for search and custom-html sidebar widgets
The search and custom-html widget types were using a non-existent
sidebar-widget class instead of the standard .widget class, causing
them to lack the spacing, border, and background styling that all
other widgets get. Updated all 4 sidebar containers.
2026-02-22 00:10:26 +01:00
Ricardo
adccb1602b fix: improve widget card styling and fix recent-comments widget
Widget cards now have white bg, border, and shadow in light mode for
clear visual separation. Each widget has bottom margin for spacing.

Recent-comments widget updated to use standard .widget/.widget-title
classes instead of custom sidebar-widget class.
2026-02-21 23:49:59 +01:00
Ricardo
f3268ed3f9 fix: load comments.js before Alpine in head (correct defer order)
Alpine CDN uses queueMicrotask to auto-start, which fires between
defer scripts. comments.js must execute before Alpine so its
alpine:init listener is registered before Alpine.start() runs.

This is the Alpine-documented pattern: "Include [component scripts]
before Alpine's core JS file."
2026-02-21 22:46:43 +01:00
Ricardo
c327221352 fix: use Alpine.data() for comments component registration
Convert commentsSection from a global function to Alpine.data()
registration via the alpine:init event. This is the proper Alpine.js
pattern for reusable components — the component is registered in
Alpine's internal registry before DOM processing begins, eliminating
script loading order issues.

Reverts the hacky approach of moving the script tag to <head>.
2026-02-21 22:40:34 +01:00
Ricardo
0450ae523b feat: add recent-comments widget to all sidebar templates
Add recent-comments widget case to data-driven routers in sidebar.njk
and homepage-sidebar.njk (was missing). Add to fallback defaults in
both sidebar.njk and blog-sidebar.njk so it shows without homepage
builder configuration.
2026-02-21 22:22:50 +01:00
rmdes
fa7bfb26ea feat: add comment system components and recent comments widget
- Comment area on post pages (IndieAuth sign-in, submit, display)
- Alpine.js client-side component for auth flow and comment CRUD
- Recent comments sidebar widget with build-time data fetching
- Include comments.js in base layout, comments.njk before webmentions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 21:56:15 +01:00
Ricardo
c1e9983c66 fix: show conversations reactions on individual post pages
Conversations items (from Mastodon/Bluesky/ActivityPub) were filtered
out by the client-side timestamp check that prevents duplicating
build-time webmentions. Since conversations data is never in the
build-time cache, bypass the filter for items with a platform field.
2026-02-20 17:52:07 +01:00
Ricardo
36f17d1a1f feat: add unfurl cards to blog page and homepage recent posts
Use two-strategy approach to work around async shortcode limitation
in deeply nested Nunjucks includes:

- blog.njk: async {% unfurl %} shortcode (top-level, works fine)
- recent-posts.njk: sync {{ url | unfurlCard | safe }} filter
  (reads from pre-populated disk cache)

eleventy.before hook scans content files and pre-fetches all
interaction URLs before templates render, ensuring the sync filter
always has data — even on first build.
2026-02-20 16:10:25 +01:00
Ricardo
ec02afb611 fix: split education and languages into separate sections
cv-education.njk no longer renders languages — use the standalone
cv-languages section instead.
2026-02-20 16:10:15 +01:00
Ricardo
66e55af7ee feat: add configurable CV page layout with builder support
CV page now reads layout config from cv-page.json when available,
supporting single-column, two-column, and full-width hero layouts with
configurable sections, sidebar widgets, and footer columns. Falls back
to the previous hardcoded layout when no config exists.
2026-02-20 15:17:32 +01:00
Ricardo
a54600b003 fix: show untyped CV items in both work and personal views
Items without a type field (existing data before type feature was added)
were only appearing in "personal" filtered views. Since the /cv/ page uses
work-only variants, all existing untyped data was hidden. Changed filtering
so untyped items appear in both work and personal views.

Also guard cv.skills dictsort in cv.njk against undefined.
2026-02-20 13:55:52 +01:00
Ricardo
f2cc855f3d fix: move pagefind indexing to start.sh for reliability
The eleventy.after hook was unreliable for pagefind because:
1. When the initial build is OOM-killed, the hook never fires
2. When the watcher starts with --incremental, the hook receives
   incremental=true even for the first full build, skipping pagefind

Pagefind is now run explicitly by start.sh after the initial Eleventy
build succeeds, with a recovery process for OOM-killed builds.
The hook retains WebSub hub notification only.
2026-02-20 13:18:16 +01:00
Ricardo
3bf0e78f74 feat: add filtered section templates for work/personal type distinction
Add thin-wrapper templates for work/personal filtering of CV sections:
- 8 new templates: cv-{experience,education,skills,interests}-{personal,work}.njk
- cv-languages.njk: standalone languages section (split from education)
- homepage-section.njk: 9 new routes for filtered variants
- cv.njk: uses work-only variants for the /cv/ page
- Base templates: filterType support in experience, education, skills, interests
- _data/cv.js: skillTypes and interestTypes fallback fields
2026-02-20 13:11:37 +01:00
Ricardo
334b8fdcf5 fix: revert unfurl from recent-posts section
Async shortcodes inside deeply nested Nunjucks includes
(homepage-builder → homepage-section → recent-posts) cause silent
template failures. Keep unfurl in top-level collection pages only.
2026-02-20 12:53:42 +01:00
Ricardo
0f496d624f feat: add unfurl cards to collection views and homepage
Show rich link preview cards in bookmarks, likes, replies, reposts
collection pages and the homepage recent posts section. URLs are
fetched once and cached — the same cache serves all templates.
2026-02-20 12:39:11 +01:00
Ricardo
41c7fae2f1 fix: use proper User-Agent for unfurl requests
The default facebookexternalhit UA causes many sites to block or
redirect-loop, resulting in timeouts. Switch to a well-identified
bot UA that sites handle correctly.
2026-02-20 12:31:42 +01:00