Gitea Contents API DELETE commits fire on:push CI; POST/PUT do not.
delete was triggering both on:push and workflow_dispatch → 2 CI runs.
Now dispatch is skipped for delete; on:push handles the rebuild.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GitHub API infers JSON without Content-Type; Gitea requires it explicitly.
Without the header, Gitea cannot parse the POST/PUT body and returns 422
Unprocessable Entity on all content write operations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gitea's Contents API differs from GitHub's:
POST /contents/{path} = create new file (no SHA)
PUT /contents/{path} = update existing file (SHA required)
store-github used PUT for createFile() because GitHub accepts PUT for
both — Gitea's PUT without SHA returns 422. Also updates the
update-fallback patch to bail to createFile() instead of falling through
to PUT-without-SHA when the file doesn't exist in the store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Posts that exist in MongoDB but not in Gitea (e.g. due to a previous
failed write) caused HTTP 500 on re-publish: updateFile() tried to read
the file's SHA, got 404, and threw instead of creating. Now detects
Not Found and falls through to a create-style PUT (no sha field).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gitea Contents API commits don't trigger on:push CI workflows.
Patches action.js to fire a workflow_dispatch to giersig.eu/indiekit-blog
after every create/update/delete/undelete so the Eleventy build runs
immediately after a post is published.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
npm fetches git deps before node_modules exist, so git URL rewriting
must happen in preinstall. Detects jail env via INDIEKIT_BIND_HOST /
INTERNAL_FETCH_URL — no-ops on local dev.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs caused replies-to-replies to be posted as 'note' type without
ActivityPub federation:
1. patch-ap-compose-default-checked: The AP reader compose form had
defaultChecked hardcoded to '@rick@rmendes.net' (original dev's handle),
so the AP syndication checkbox was never pre-checked. Fixed to use
target.checked from the Micropub q=config response, which already
carries checked: true for the AP syndicator.
2. patch-ap-mastodon-reply-threading: POST /api/v1/statuses deferred
ap_timeline insertion until the Eleventy build webhook fired (30–120 s).
If the user replied to their own new post before the build finished,
findTimelineItemById returned null → inReplyTo = null → no in-reply-to
in JF2 → post-type-discovery returned 'note' → reply saved at /notes/
and sent without inReplyTo in the AP activity, breaking thread display
on remote servers. Fixed by eagerly inserting the provisional timeline
item immediately after postContent.create() ($setOnInsert — idempotent;
syndicator upsert later is a no-op).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
uploadMedia() had no content-type check, so an HTML login-redirect response
from an auth-protected internal endpoint was uploaded to Bluesky as a blob
with encoding "text/html". uploadBlob() accepts it, but record validation
rejects the post with 'Expected "image/*" (got "text/html")'.
The patch mirrors the guard already present in uploadImageFromUrl() and also
wraps per-photo uploads in try/catch so one bad photo doesn't abort the
entire syndication — other photos and the post text are still published.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without nginx forwarding Host/X-Forwarded-Proto headers, fromExpressRequest()
builds a wrong URL (e.g. http://127.0.0.1:3000/...) that Fedify doesn't
recognise as its own base URL — so it calls next() and requests fall through
to auth middleware, returning 302 to the login page. This breaks webfinger,
actor lookups, and AP inbox delivery.
The patch overrides the URL construction in createFedifyMiddleware() and
fromExpressRequest() to use the configured publicationUrl as the base,
bypassing the dependency on proxy headers entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/og/{slug}.png is only generated by Eleventy for replies, bookmarks, and
articles — not for photo post types. Photo posts now use properties.photo[0]
directly as the ActivityPub image field; all other post types keep the
/og/{slug}.png fallback. Patch handles all known file states (original
upstream, v1 patched, already v2).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The OLD_SNIPPET had wrong indentation (8 spaces vs 6) and was missing the
method guard line added in the fork:
if (req.method !== "GET" && req.method !== "HEAD") return next();
Without this fix the patch silently skips patching and webfinger continues
to return 302 → 401 on fediverse delivery.
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
- Add scripts/patch-ap-og-image.mjs to both postinstall and serve so OG
preview images are included in ActivityPub activities (flat URL slug
extraction instead of date-based regex).
- Add scripts/patch-ap-webfinger-before-auth.mjs (new file) to both
postinstall and serve. Extends contentNegotiationRoutes Fedify delegation
to also forward /.well-known/* paths, ensuring webfinger is served by
Fedify before indiekit auth middleware can issue a 302 redirect. Fixes
401 Unauthorized errors from remote fediverse instances.
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
`getEndpointUrls()` resolved relative endpoint paths (e.g. `/media`) using
`getUrl(request)`, which returns `http://` because Express sees HTTP from nginx
without trust proxy. This produced `http://blog.giersig.eu/media` as the
endpoint attribute in the file-input component, causing Safari to block the
fetch as mixed content ('Load failed').
Fix: prefer `application.url` (the configured HTTPS base URL) over
`getUrl(request)` when resolving relative endpoint paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The patch now recognises chore: and refactor: commit prefixes as their
own categories instead of lumping them into "other".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All of the following are now native in svemagie/indiekit-endpoint-activitypub:
- patch-ap-url-lookup-api (AP URL lookup endpoint)
- patch-ap-allow-private-address (allowPrivateAddress in federation-setup)
- patch-ap-like-note-dispatcher (fake-Note revert)
- patch-ap-like-activity-id (canonical Like activity id URI)
- patch-ap-like-activity-dispatcher (Like setObjectDispatcher)
- patch-ap-url-lookup-api-like (likeOf URL in /api/ap-url)
- patch-ap-remove-federation-diag (inbox diagnostic log removed)
- patch-ap-og-image (orphan, not in package.json)
- patch-ap-normalize-nested-tags (orphan, no-op)
- patch-ap-object-url-trailing-slash (orphan, no-op)
patch-ap-skip-draft-syndication kept — draft guard in syndicate() not yet in fork.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Repost commentary changes are now native in svemagie/indiekit-endpoint-activitypub.
Patch is no longer needed and was causing a duplicate repost block on every deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix D's old snippet (the bare `} else {` content block) still matched after
the fork absorbed the repost-commentary changes natively, causing a duplicate
`} else if (postType === "repost") {` block to be inserted on every deploy
and preventing startup. Added NATIVE_MARKER check for the fork's own repost
branch so the patch skips cleanly when the changes are already present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The fork added an Accept-header upgrade block between the diagnostic
log and the return statement, breaking the OLD_SNIPPET match. Patch
now handles both the original form and the updated form.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
livefetch v6:
- Adds console.log showing which property links were built per post
(e.g. "in-reply-to" for replies) — makes it debuggable without server access
- Fixes retryPatchedBlock to include the two comment lines the retry patch
actually inserts (was missing them, causing "Target block not found" on
fresh upstream → retry → livefetch path)
- Adds v5 to priorMarkersNoContinue with contentToProcess-line end detection
so v5 → v6 in-place upgrade works correctly
reset-stale: bump to v11 to retry ca3d8 and any other posts stuck
before v5/v6 deployment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new patches:
- patch-ap-skip-draft-syndication: guards the AP syndicator's syndicate()
method against draft posts (mirrors existing unlisted visibility check)
- patch-microsub-compose-draft-guard: forwards post-status from microsub
compose to Micropub and suppresses mp-syndicate-to targets for drafts
The syndicate endpoint DB queries already filter post-status != draft
(patch-federation-unlisted-guards). These patches add defence in depth
at the AP syndicator and at the microsub compose submission layer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous fix incorrectly generated /og/{year}-{month}-{day}-{slug}.png
but the Eleventy blog generates OG images at /og/{slug}.png (e.g. /og/2615b.png).
Remove the unnecessary date extraction and simplify to slug-only.
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
Root cause: jf2-to-as2.js (both 842fc5af and 45f8ba9) uses a regex
expecting date-based URLs like /articles/YYYY/MM/DD/slug/ to extract
the post date for the OG image filename. This blog uses flat URLs like
/articles/slug/ so the regex never matches — no `image` property is
set on Note/Article ActivityPub objects and fediverse clients show no
preview card.
Fix:
- Add scripts/patch-ap-og-image.mjs: replaces the URL-pattern regex with
slug-from-last-path-segment + date-from-properties.published, producing
/og/{year}-{month}-{day}-{slug}.png matching the Eleventy OG filenames.
Handles both 842fc5af (slug-only URL) and 45f8ba9 (date+slug) variants.
Applied to both jf2ToActivityStreams() and jf2ToAS2Activity().
- Register patch in package.json postinstall and serve after patch-ap-repost-commentary.
- Bump package-lock.json to 45f8ba9 fork commit (correct date+slug filename
format, likes-as-bookmarks, announce cc reverted).
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
The previous fix incorrectly generated /og/{year}-{month}-{day}-{slug}.png
but the Eleventy blog generates OG images at /og/{slug}.png (e.g. /og/2615b.png).
Remove the unnecessary date extraction and simplify to slug-only.
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
retry patch: regex now matches [patched:livefetch] and [patched:livefetch:vN]
so it silently skips for any livefetch version, not just v2.
reset-stale: bump to v10 to retry posts stuck during v5 rollout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On fresh installs where the old wrong patch was never applied, Like was
absent from the @fedify/fedify/vocab imports, causing a ReferenceError
at startup. The dispatcher patch now adds Like to the import block if
missing, making it self-contained and install-order independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the semantically incorrect fake-Note approach with strict AP protocol compliance:
- patch-ap-like-note-dispatcher: rewritten to revert the fake-Note block
- patch-ap-like-activity-id: adds canonical id URI to Like activities (AP §6.2.1)
- patch-ap-like-activity-dispatcher: registers setObjectDispatcher(Like, ...) so
/activitypub/activities/like/{id} is dereferenceable (AP §3.1)
- patch-ap-url-lookup-api-like: /api/ap-url now returns the likeOf URL for AP-likes
so the "Also on: Fediverse" widget's authorize_interaction flow opens the
original Mastodon post on the remote instance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Micropub's replaceEntries() stores single-value arrays as plain strings
(JF2 normalization). Spreading a string into [...str] gives individual
characters, so hasSyndicationUrl() never matches existing syndication URLs
and alreadySyndicated is always false — causing re-syndication on every
webhook trigger.
Fix: use [].concat() which safely handles both string and array values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: blog.giersig.eu DNS resolves internally to 10.100.0.10 (the
indiekit admin nginx), which returns the login page for post URLs of
certain types (notes, photos, replies). Live page fetching is inherently
unreliable in this split-DNS / jailed setup.
The fix: indiekit already stores all microformat target URLs in MongoDB
(in-reply-to, like-of, bookmark-of, repost-of) and content.html has
inline links. We can build a synthetic h-entry HTML snippet directly
from post.properties — no network fetch required for the source post.
Bumps livefetch patch to v5:
- Replace live page fetch with synthetic HTML built from post.properties
- Handles string values, mf2 objects ({properties.url[0]}), and plain
value strings for each microformat property
- Simplifies patch script: single full-block replacement handles all
prior versions (v1–v4) via marker detection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: INTERNAL_FETCH_URL (10.100.0.10) points to the nginx reverse
proxy in front of indiekit's admin interface. Post URLs like /bookmarks/…
require authentication there, so the fetch returned the login page
("Anmelden - Indiekit") which has no .h-entry.
The blog HTML is served by an external host (GitHub Pages), reachable
from the jail over the public URL. INTERNAL_FETCH_URL should only be
used for indiekit API calls, not for fetching blog post pages.
Bumps livefetch patch to v4:
- Remove INTERNAL_FETCH_URL rewrite for live page fetches
- Fetch from postUrl (public URL) directly by default
- Add WEBMENTION_LIVEFETCH_URL env var as opt-in override for setups
where a local static server can serve blog pages faster
- Add v3→v4 in-place upgrade logic to the patch script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: jf2-to-as2.js (both 842fc5af and 45f8ba9) uses a regex
expecting date-based URLs like /articles/YYYY/MM/DD/slug/ to extract
the post date for the OG image filename. This blog uses flat URLs like
/articles/slug/ so the regex never matches — no `image` property is
set on Note/Article ActivityPub objects and fediverse clients show no
preview card.
Fix:
- Add scripts/patch-ap-og-image.mjs: replaces the URL-pattern regex with
slug-from-last-path-segment + date-from-properties.published, producing
/og/{year}-{month}-{day}-{slug}.png matching the Eleventy OG filenames.
Handles both 842fc5af (slug-only URL) and 45f8ba9 (date+slug) variants.
Applied to both jf2ToActivityStreams() and jf2ToAS2Activity().
- Register patch in package.json postinstall and serve after patch-ap-repost-commentary.
- Bump package-lock.json to 45f8ba9 fork commit (correct date+slug filename
format, likes-as-bookmarks, announce cc reverted).
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
Without a Host header, nginx routes internal fetches to the wrong vhost
(sees the internal IP as Host), returning a page with no .h-entry and
causing all posts to retry indefinitely.
Bumps livefetch patch to v3:
- Sends `Host: blog.giersig.eu` when fetching via internal URL so nginx
routes to the correct virtual host
- Logs the actual fetchUrl on every internal fetch
- Logs the first 200 chars of the response body when h-entry check fails
so the root cause (wrong vhost, indiekit page, etc.) is visible
- Adds v2→v3 in-place upgrade logic to the patch script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove patch-preset-eleventy-ai-frontmatter: upstream now writes AI
frontmatter natively using hyphenated keys (ai-text-level etc.)
- Remove patch-endpoint-posts-ai-cleanup: upstream beta.44 natively
removes empty ai-text-level/ai-code-level/ai-tools/ai-description fields
- Remove patch-endpoint-posts-ai-fields: upstream beta.44 has AI form UI
inline in post-form.njk; our separate templates would have duplicated fields
- Remove patch-micropub-ai-block-resync: one-time stale-block migration,
no longer relevant
- Remove patch-endpoint-posts-prefill-url: upstream beta.44 has native
prefill from query params; our patch would have conflicted
- Remove patch-endpoint-posts-search-tags: upstream beta.44 has native
search/filter/sort UI; patch already detected this and was a no-op
- Bump @rmdes/indiekit-endpoint-posts beta.25→beta.44,
override beta.41→beta.44
- Update indiekit.config.mjs: remove camelCase ai field names from
all postTypes.fields (ai-* fields now rendered inline by upstream)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reposts with a body (commentary) were silently broken in two ways:
1. jf2ToAS2Activity() always emitted a bare Announce pointing at the
external URL (e.g. fromjason.xyz). That URL doesn't serve AP JSON,
so Mastodon couldn't fetch the object and dropped the activity from
followers' timelines — the post only appeared when explicitly searched.
2. jf2ToActivityStreams() (content negotiation / search) hard-coded the
Note content to just '🔁 <url>', completely ignoring properties.content.
Fix via patch-ap-repost-commentary.mjs (4 targeted replacements):
- jf2ToAS2Activity(): skip the Announce early-return when commentary is
present and fall through to the existing Create(Note) path instead.
Pure reposts (no body) keep the Announce behaviour unchanged.
- jf2ToAS2Activity() content block: add a repost branch that formats
the Note as '<commentary><br><br>🔁 <url>' (mirrors bookmark/like).
- jf2ToActivityStreams(): extract commentary and prepend it to the Note
content when present.
Patch registered in both postinstall and serve chains.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds patch-webmention-sender-hentry-syntax.mjs to fix a typo in
@rmdes/indiekit-endpoint-webmention-sender@1.0.8 that prevents the
module from loading:
_html.includes("h-entry"") → _html.includes("h-entry")
The extra closing quote causes a SyntaxError at startup, which means
the webmention sender never runs and the background sync never starts.
Patch runs before the other webmention-sender patches in both
postinstall and serve so the file is valid JS before further transforms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- retry: silently skip when livefetch:v2 marker is present instead of
logging a misleading "target snippet not found (package updated?)"
warning on every startup
- livefetch: match `h-entry"` or `h-entry ` instead of bare `h-entry`
to avoid false positives from body text containing the string
- reset-stale: update comment to reference livefetch v2 (not retry)
as the patch that prevents recurrence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: when the livefetch got a 200 response that was actually an
error page (nginx 502 HTML, login redirect, error template), it had no
.h-entry so extractLinks found 0 links — permanently marking the post
as sent with empty results.
Changes:
- livefetch v2: check fetched HTML contains "h-entry" before using it;
if missing, skip and retry next poll instead of falling back to stored
content (which also lacks microformat links for likes/reposts/bookmarks)
- livefetch v2: can detect and upgrade from v1 patch in-place
- reset-stale v9: also matches the v1.0.6+ detail format (empty arrays)
to catch posts stuck by the error-page bug
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>