Commit Graph

195 Commits

Author SHA1 Message Date
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
Ricardo
e0cbf8121e fix: unfurl shortcode concurrency control and failure caching
- Increase timeout from 10s to 20s
- Cache failures for 1 day (avoids retrying every build)
- Add concurrency limiter (max 5 parallel requests)
- Refactor into renderCard/renderFallbackLink helpers
2026-02-20 12:20:28 +01:00
Ricardo
9ab4ebb84a feat: add unfurl cards to reply context (likes, bookmarks, replies, reposts)
The target URL in likes, bookmarks, replies, and reposts now renders
as a rich OpenGraph card via the unfurl shortcode instead of a bare
link. The raw URL remains below the card for h-cite microformat
compatibility. Results are cached in .cache/unfurl/ for 1 week.
2026-02-20 12:01:55 +01:00
Ricardo
897daca686 feat: add unfurl shortcode for rich link preview cards
Adds {% unfurl "URL" %} shortcode that renders any URL as a rich card
with OpenGraph metadata (title, description, image, favicon). Uses
unfurl.js locally — no external API dependency. Results cached for 1
week in .cache/unfurl/. Also fixes Mastodon embed server config
(mstdn.social → indieweb.social).
2026-02-20 11:29:11 +01:00
Ricardo
656a70eb0e feat: graceful no-JS fallback and noscript handling
- Add <noscript><style> in base.njk that unhides x-cloak/x-show content,
  hides FAB and tab buttons when JS is disabled (content stacks instead)
- Add noscript message on search page with links to blog/categories
- Add noscript banner on interactions page explaining inbound tab needs JS
2026-02-19 17:35:21 +01:00
Ricardo
03ace58be5 feat: add ActivityPub badge and platform detection for interactions
- Add ActivityPub/Fediverse platform badge (purple, network icon) to
  interactions page alongside existing Mastodon and Bluesky badges
- Detect platform from Bridgy source URLs and author URLs for
  webmention.io items that lack a platform field
- Filter self-referencing syndication URLs from "Also on" footer so
  self-hosted AP posts don't show a redundant link back to the site
2026-02-19 16:52:44 +01:00
Ricardo
cb68b7be00 fix: add rel=nofollow to FAB admin links
AI crawlers (GPTBot) were following edit/create links in the static
HTML, flooding the server with /posts/edit and /session/login requests.
Adding nofollow tells well-behaved crawlers to skip these admin-only links.
2026-02-18 22:17:38 +01:00
rmdes
fc9f5968da feat: dual-fetch from conversations API for enriched interaction data
Fetch from both /webmentions/api/mentions and /conversations/api/mentions,
merge results with conversations items taking priority (richer metadata),
and display platform badges (Mastodon/Bluesky icons) on interaction cards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:28:01 +01:00
Ricardo
216cfa21bd fix: use directories.output for pagefind indexing
The eleventy.after hook's dir.output reflects the config default (_site),
not the --output CLI flag used by start.sh. Use directories.output which
reflects the actual resolved output path.
2026-02-18 17:42:11 +01:00
Ricardo
a0040c949d fix: pagefind output directory to match template references
Templates reference /pagefind/pagefind-ui.js but pagefind defaulted
to _pagefind/ (with underscore). Added --output-subdir pagefind flag
and updated ignores to match.
2026-02-18 17:31:00 +01:00
Ricardo
2ebe63ffff fix: use eleventyComputed for OG slug to avoid Nunjucks race condition
Eleventy 3.x renders Nunjucks templates in parallel, causing page.url
to return wrong values in {% set %} tags. This caused OG images to be
mismatched between pages (e.g., bookmark showed note's OG image).

Move ogSlug and hasOgImage computation to eleventyComputed, which runs
during the sequential data cascade phase before parallel rendering.
The computed values are then available as plain template variables.

Refs: https://github.com/11ty/eleventy/issues/3183
2026-02-18 17:17:30 +01:00
Ricardo
b065dbfed7 fix: use full date-prefixed filenames for OG images
The Nunjucks race condition in Eleventy 3.x affects page.url too —
its value changes between {% set %} and {{ }} within the same
template render during parallel builds. Instead of trying to derive
slugs from page data, name OG images with the full filename
(including date prefix) to match URL path segments exactly.
2026-02-18 17:08:05 +01:00
Ricardo
8d800e2c28 fix: derive OG slug from page.url to avoid Nunjucks race condition
page.fileSlug suffers from a race condition in Eleventy 3.x parallel
rendering where Nunjucks shares state across template compilations,
causing slugs to get mixed up between pages. page.url is always
correct, so derive the OG slug from it instead.
2026-02-18 16:48:11 +01:00
Ricardo
cd8a218afb fix: exclude .cache/og from watcher to prevent build loop
OG image generation writes to .cache/og/ which the watcher detects
as file changes, triggering another build that runs OG generation
again in an infinite loop.
2026-02-18 16:10:55 +01:00
Ricardo
db1b922943 fix: generate OG images on watcher rebuilds
Remove the incremental skip guard from OG generation. The manifest
caching already handles performance — only new posts without an
existing OG image get generated (~200ms each). Without this fix,
posts created via Micropub never got OG images until container restart.
2026-02-18 15:44:10 +01:00
Ricardo
538e94f582 fix: declare collection dependencies for incremental builds
Add eleventyImport.collections to templates that access collections
via Nunjucks loops (not pagination frontmatter). Without this,
Eleventy's --incremental mode skips these pages when content changes.

- index.njk: posts, feed (homepage recent posts widget)
- feed.njk: feed (RSS must update with new posts)
- feed-json.njk: feed (JSON feed must update with new posts)
- slashes.njk: pages (slash pages index)
- categories-index.njk: categories (categories index)
2026-02-18 14:42:02 +01:00
Ricardo
ff157c838d feat: guard Eleventy hooks for incremental rebuild mode
Skip OG image generation, Pagefind indexing, and WebSub notification
during incremental rebuilds. These expensive operations only run on
full builds (container restart), not on every content change.
2026-02-18 12:23:10 +01:00
Ricardo
daf813e192 fix: wrap webmentions widget in is-land for lazy loading
The webmentions sidebar widget uses Alpine.js + fetch API but was
missing the is-land on:visible wrapper that all other JS-dependent
sidebar widgets have. This defers its initialization until scrolled
into view, consistent with social-activity, github-repos, funkwhale,
blogroll, and feedland widgets.
2026-02-18 12:20:38 +01:00
Ricardo
0d63dde15c fix: wrap tables in table-saw elements for responsive behavior
The @zachleat/table-saw component requires tables to be wrapped in
<table-saw> elements. Added an Eleventy transform to do this
automatically for all HTML output.
2026-02-18 12:12:28 +01:00
Ricardo
f7bc5d9112 feat: add posting frequency sparklines to all post type pages
Each collection page (articles, notes, photos, bookmarks, likes,
replies, reposts) now shows its own sparkline next to the heading,
showing that specific post type's frequency over the last 12 months.
2026-02-18 12:02:49 +01:00
Ricardo
113d0d55dd fix: generate sparkline SVG inline instead of using external service
The v1.sparkline.11ty.dev service returns 429 errors. Generate the
sparkline polyline SVG at build time and inline it directly.
2026-02-18 11:42:09 +01:00
Ricardo
c3eb04570c feat: add zachleat.com-inspired theme enhancements
- Add time-difference web component for relative dates
- Add @zachleat/table-saw for responsive tables
- Add webmention facepile styling with bookmarks support
- Add OG image thumbnails to post navigation
- Add @11ty/is-land for lazy widget hydration
- Wrap sidebar widgets in is-land for deferred loading
- Lazy-load webmention avatars with is-land
- Add @zachleat/filter-container for blog archive filtering
- Add posting frequency sparkline to blog header
- Inline critical CSS and defer full stylesheet loading
2026-02-18 11:16:33 +01:00
Ricardo
e5b0fd7dc6 fix: gracefully handle htmlmin parse errors instead of crashing build
Malformed HTML (e.g. unescaped quotes in iframe title attributes) caused
html-minifier-terser to throw a fatal parse error, killing the entire
Eleventy build. Now catches the error, logs a warning, and returns the
unminified content so the build completes.
2026-02-18 09:59:00 +01:00
Ricardo
8503237d4d fix(og): match Eleventy's page.fileSlug by stripping date prefix
Eleventy v3 parses YYYY-MM-DD- from filenames and removes it from
page.fileSlug. The OG generator was using the full filename (with
date prefix) causing a slug mismatch — hasOgImage filter checked
for 'slug.png' while the file was 'YYYY-MM-DD-slug.png'.

Also removes debug logging from hasOgImage filter.
2026-02-18 09:42:10 +01:00
Ricardo
719ce101b7 debug: add OG filter debug logging 2026-02-18 09:18:52 +01:00
Ricardo
de09db6171 fix: skip hidden images in extractFirstImage, fix OG subprocess NODE_OPTIONS
Two fixes:
- extractFirstImage filter now skips <img> tags with the hidden attribute,
  preventing the author avatar microformat from being used as og:image
- Clear NODE_OPTIONS in the OG subprocess env and pass --max-old-space-size
  as a direct CLI flag to avoid conflict with parent process settings
2026-02-18 08:59:25 +01:00