Commit Graph

54 Commits

Author SHA1 Message Date
Ricardo
22c151bb02 feat: add categoryFeeds collection for per-category RSS/JSON feeds 2026-02-24 22:39:49 +01:00
Ricardo
338bd3cc64 fix: correct alternate link URL for markdown-agents
Add stripTrailingSlash filter and use it in the link tag so the
alternate URL is /articles/.../slug.md (matching nginx routing)
instead of /articles/.../slug/index.md.
2026-02-24 20:53:04 +01:00
Ricardo
c1cd837845 fix: move markdown-agents generation from pagination template to eleventy.after hook
Eleventy 3.x's async internals crash when pagination templates access
collection item properties, triggering the frontMatter getter. Replace
the article-markdown.njk pagination template with post-build file
generation using gray-matter to read source files directly.
2026-02-24 20:46:23 +01:00
Ricardo
356d074700 fix: use rawMarkdownBody filter instead of template.frontMatter.content
Eleventy 3.x no longer allows synchronous access to template internals
from pagination templates. Replace article.template.frontMatter.content
with a custom filter that reads the source file via gray-matter.
2026-02-24 20:32:46 +01:00
Ricardo
e56e2c67a5 fix: use Eleventy transform to resolve OG images from outputPath
The Eleventy 3.x parallel rendering race condition (#3183) makes
page.url unreliable in templates — it changes between lines during
concurrent processing. All previous approaches (eleventyComputed,
capturing page.url early with {% set %}) failed because the page
object is shared and mutated by parallel renders.

The transform approach works because outputPath is passed as a
function parameter (not read from a shared object) and IS correct
since files are written to the right location. The transform:

- Derives the OG slug from outputPath pattern matching
- Replaces __OG_IMAGE_PLACEHOLDER__ with the correct OG image URL
- Replaces __TWITTER_CARD_PLACEHOLDER__ with the correct card type
- Fixes og:url and canonical URL from outputPath
2026-02-24 20:31:24 +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
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
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
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
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
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
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
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
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
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
Ricardo
86cbc1ee5d fix: run OG image generation in subprocess to prevent OOM kill
The OG generation in the eleventy.before hook consumed too much memory
alongside Eleventy's data cascade, causing the Eleventy process to be
OOM-killed on Cloudron. Fix by running OG generation in a separate
child process with its own 768MB heap limit. Also write the manifest
incrementally (every 10 images) to preserve progress if interrupted.
2026-02-18 08:49:23 +01:00
rmdes
afc394525b feat: add textcasting support, feed attachments, and protocol badges
- Add _textcasting extension to JSON feed with support/monetization config
- Add feedAttachments filter for photo/audio/video media in feed items
- Add content_text and date_modified fields to feed items
- Add protocol badges (ATmosphere, Fediverse, Web) on reply posts
- Add support configuration via environment variables in site data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 08:42:59 +01:00
Ricardo
fe06fe3f4f feat: auto-generate OpenGraph images for posts without photos
Uses Satori + @resvg/resvg-js to create branded 1200x630 social
preview cards at build time. Cards show post title, type badge,
date, and site name on a dark background with blue accent.

Generated images are cached in .cache/og/ (persistent on Cloudron)
and passthrough-copied to the output. Posts with photos continue
using their own images. Untitled posts (notes) use body text.
2026-02-18 08:37:50 +01:00
Ricardo
4e2f579e71 feat: add heading anchors for direct linking to article sections
Uses markdown-it-anchor to generate linkable IDs on h2-h4 headings.
Headings become clickable links with a subtle # indicator on hover.
Includes scroll-margin-top so anchored headings don't hide behind
the sticky header.
2026-02-17 19:09:09 +01:00
Ricardo
a4c121d203 fix: make post-navigation and webmentions sidebar widgets functional
Post navigation now uses previousInCollection/nextInCollection filters
to find adjacent posts (avoids Nunjucks loop scoping bug). Webmentions
sidebar widget now uses webmentionsForUrl and webmentionsByType filters
instead of non-existent filter function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 08:53:35 +01:00
Ricardo
cf3586eadd feat: data-driven blog sidebars and fix recentPosts glob
Fix recentPosts collection glob (content/posts/ → content/) so the
widget actually finds posts. Extract blog-sidebar widgets into reusable
partials. Make both sidebar.njk (listing pages) and blog-sidebar.njk
(post pages) configurable via homepageConfig.blogListingSidebar and
homepageConfig.blogPostSidebar, with fallback to current hardcoded
widgets for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:13:25 +01:00
Ricardo
ddbc983505 feat: add syntax highlighting for code blocks
Integrates @11ty/eleventy-plugin-syntaxhighlight (PrismJS) for
build-time syntax highlighting of fenced code blocks. Includes
a custom GitHub-inspired theme with dark mode support (.dark class).

All existing articles with triple-backtick code blocks will
automatically get highlighting on next Eleventy rebuild.

Also fixes overflow-x: hidden on .prose/.e-content that was
clipping scrollable code blocks — changed to overflow-x: clip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 10:21:31 +01:00
Ricardo
c5cd3c2c75 fix: update webmention API URLs for new moderation plugin
Change all webmention fetch URLs from /webmentions-api/api/mentions
to /webmentions/api/mentions to match the new @rmdes/indiekit-endpoint-webmention-io
plugin which replaces both the upstream viewer and the proxy plugin.

Build-time feed now fetches from local Indiekit API instead of
webmention.io directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:24:28 +01:00
Ricardo
b448aaa0e0 feat: add email obfuscation to protect from spam bots
- Add obfuscateEmail Eleventy filter that converts email to HTML entities
- Apply to h-card.njk (both mailto: href and display text)
- Apply to blog-sidebar.njk hidden data element
- HTML entities block ~95% of spam harvesters while remaining valid
  for IndieWeb microformat parsers (u-email for rel="me" auth)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:21:09 +01:00
Ricardo
e78894cc28 fix: match homepage WebSub topic URL with trailing slash
The HTML self link produces https://rmendes.net/ (with slash)
but hub was notified with https://rmendes.net (no slash).
Hub treats these as different topics.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:34:11 +01:00
Ricardo
c282ef4a1d fix: notify WebSub hub about homepage URL too
Subscribers discovering via HTML rel="self" subscribe to the
homepage topic, not the feed URL. Must notify hub for all
topic URLs so those subscriptions receive updates.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 17:54:07 +01:00
Ricardo
c17ca030c8 feat: add WebSub support for real-time feed updates
Advertise WebSub hub (websubhub.com) in three discovery layers:
- HTML <link rel="hub"> in page head
- <atom:link rel="hub"> in RSS feed
- "hubs" array in JSON Feed 1.1

Notify hub after each Eleventy build so subscribers receive
push updates when new content is published.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 16:34:37 +01:00
Ricardo
e5d699cffc fix: use Pagefind 1.x path and exclude non-HTML files from index
Update Pagefind asset URLs from /_pagefind/ to /pagefind/ to stop
the pre-1.0 compatibility mode that was writing the index twice.
Also ignore CLAUDE.md and README.md in Eleventy to prevent them
from being processed into HTML pages without an <html> element.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 13:54:51 +01:00
Ricardo
d9c84cad80 feat: add Pagefind client-side search
Add Pagefind indexing after each Eleventy build with a search page at
/search/. Indexes main content only (sidebars excluded), supports dark
mode theming and URL query parameters (?q=).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:49:05 +01:00
Ricardo
625ad5c16c fix: add watchIgnores to prevent rebuild loop
When Eleventy's output (_site) is a symlink to /app/data/site, the watcher
was detecting changes to its own output and triggering infinite rebuilds.
This adds explicit ignores for both the symlink and its target path.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 23:42:33 +01:00
Ricardo
ddf27fc132 perf: skip htmlmin during watch rebuilds, disable minifyCSS/minifyJS
htmlmin transform was consuming 84% of build time (321s out of 384s).
Two changes:
- Only run htmlmin during initial build (ELEVENTY_RUN_MODE === "build"),
  skip during watch-mode rebuilds for faster content updates
- Set minifyCSS and minifyJS to false to avoid expensive CSS/JS parsing

Also updates CLAUDE.md paths for indiekit-dev workspace move.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:40:57 +01:00
Ricardo
a148a90cc7 fix: include content/pages/*.md in pages collection 2026-02-02 12:03:30 +01:00
Ricardo
c01bdd331f feat: add slashes index to navigation
- Add "/" link to desktop and mobile nav pointing to /slashes/
- Update pages collection to find content/*.md (root-level pages)
- Keeps About and Now as featured nav items, with / for all pages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:58:59 +01:00
Ricardo
ae8bd83a0b feat: add support for slash pages (root-level pages)
- Add pages collection in eleventy.config.js
- Add page.njk layout for slash pages
- Add /slashes/ listing page showing all site pages
- Pages created via Indiekit go to /{slug}/ instead of /content/pages/

Inspired by https://slashpages.net

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 11:13:20 +01:00
Ricardo
1f3fe00ce8 fix: multiple frontend issues
- Add Alpine.js Collapse plugin for x-collapse directive
- Create favicon.svg and favicon.ico with proper linking
- Fix default-avatar references (use existing .svg instead of .png)
- Add favicon.ico to passthrough copy

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:08:01 +01:00
Ricardo
8da928a91c fix: use timestamp filter for buildtime instead of date filter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 10:22:34 +01:00
Ricardo
922ae40460 feat: add client-side webmention fetcher for real-time updates
- Add js/webmentions.js to fetch new webmentions from webmention.io API
- Supplements build-time cached webmentions with real-time data
- Shows new webmentions with 'NEW' badge and visual ring highlight
- Uses safe DOM methods to prevent XSS vulnerabilities
- Data attributes on webmentions container provide target URL and build time

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 10:11:48 +01:00