Add shared save-later.js module and per-item save buttons to
blogroll, podroll, listening, and news pages. Buttons are hidden
by default and only visible when logged in. Posts to the readlater
plugin API at /readlater/save.
Alpine.js component that lets visitors click any image inside
article content to view it fullscreen with keyboard navigation
(arrow keys, Escape to close) and prev/next buttons.
The extractFirstImage filter picks up <img> tags from the full rendered
page content, including sidebar widgets (like recent post thumbnails).
This caused og:image to reference sidebar OG images from OTHER posts
instead of falling through to the __OG_IMAGE_PLACEHOLDER__ that the
og-fix transform resolves from outputPath.
Only ogPhoto and image (from frontmatter) are now used as explicit
image sources. All other cases use the placeholder resolved by the
og-fix transform.
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
Generate index.md alongside index.html for /articles/ at build time.
Agents can access clean Markdown via .md URL extension or Accept:
text/markdown content negotiation. Includes configurable content-signal
policy (ai-train, search, ai-input) and a master on/off toggle via
MARKDOWN_AGENTS_ENABLED env var.
Pages with permalink:false (like about.njk) have page.url as false,
which crashes inline string operations. Use the ogSlug filter with
(page.url or "") guard to handle falsy values safely. Also removes
debug comment from previous debugging session.
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).
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.
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).
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.
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."
- 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>
- 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
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.
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
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.
- 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
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.
When viewing a post that has mpUrl in its frontmatter, the FAB menu
shows an "Edit this post" link at the top that redirects to the
Indiekit admin edit form via /posts/edit?url=<mpUrl>.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The defer + DOMContentLoaded queue approach failed silently when
pagefind-ui.js couldn't load. Moving the script to end of body
ensures all DOM elements exist and processes the queue immediately
after the script loads.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pagefind CSS/JS is now loaded once in base.njk <head> with defer.
A tiny initPagefind() helper queues widget inits until DOMContentLoaded
when PagefindUI is available. Removes duplicate <link>/<script> tags
from all sidebar widgets, search page, and 404 page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New /changelog/ page with Alpine.js tabbed interface showing commits
across all indiekit repos, categorized into 7 groups with load-more
- Footer refactored from minimal feed links to responsive 4-zone grid:
Navigate, Content, Connect (social links), Meta (feeds + changelog)
- Footer uses grid-cols-2 on mobile, grid-cols-4 on desktop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
/now is already in the /slashes submenu dropdown, so having it
top-level was redundant. /cv gets promoted to main navbar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move CV content to a standalone /cv page that reuses the section partials
from the homepage builder. Simplify home.njk from a 3-tier to 2-tier
fallback (plugin config OR recent posts + explore links). Add /cv to
the slash pages navigation dropdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The homepage builder's sidebar bypass was using homepageConfig (a global
Eleventy data file) to suppress the sidebar on ALL pages, not just the
homepage. Now only bypasses when page.url == "/" so blog, post type, and
plugin pages keep their normal sidebar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- base.njk: skip hardcoded sidebar when homepage builder is active
- homepage-builder.njk: implement 3 layouts (single-column, two-column,
full-width-hero) with CSS grid and sidebar rendering
- homepage-section.njk: section type dispatcher (extracted for reuse)
- homepage-sidebar.njk: maps widget types to existing widget partials
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip personal data from templates so the theme ships clean for any
deployer. Collection pages now use generatePageOnEmptyData so empty
post types show encouraging placeholders instead of 404s. Navigation
is conditional on enabled post types and installed plugins. Sidebar
widgets split into individual components with plugin-aware visibility.
Slashes page explains required plugins for root-level page creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WebSub spec requires both rel="hub" and rel="self" for
discovery. websub.rocks conformance test failed without self.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
Auth detection via /session/login probe with sessionStorage cache.
Dashboard link appears in desktop and mobile nav when authenticated.
Floating action button with quick-create menu for Note, Article,
Photo, Bookmark, and Page post types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
- Add theme toggle button at bottom of mobile nav
- Share toggleTheme function between desktop and mobile
- Style mobile toggle to match nav item appearance
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- All slash pages now under one "/" dropdown
- Dynamic pages (from Indiekit) appear first
- Activity feeds (github, listening, funkwhale, youtube, news) below divider
- Remove standalone Activity dropdown and /news link
- Add divider styles for desktop and mobile nav
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Desktop: "/" becomes dropdown showing all pages from collections.pages
- Mobile: "/" becomes collapsible section with all dynamic pages
- New pages created via Indiekit automatically appear in nav
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
Add /now/ link to both desktop and mobile navigation menus,
placing it after About as a standard slash page.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Bluesky URLs now get rel='me atproto' for verification
- Added fediverse:creator meta tag for Mastodon creator attribution
- Meta tag populated from MASTODON_INSTANCE and MASTODON_USER env vars
The photo property can be an array for multi-photo posts.
Normalize photo to first element if array, and avoid startsWith on non-strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Blog dropdown with all post types (articles, notes, photos, bookmarks, likes, replies, reposts)
- Add Activity dropdown with GitHub, Listening, Funkwhale, YouTube
- Add /news and /interactions links to main nav
- Add mobile-responsive collapsible sections with Alpine.js
- Add CSS for dropdown menus and mobile nav sections
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>