Files
indiekit-server/scripts/patch-ap-compose-default-checked.mjs
Sven 97d99976ea fix(ap): fix reply threading — pre-check AP syndication and resolve in_reply_to_id immediately
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>
2026-03-30 08:12:43 +02:00

91 lines
3.1 KiB
JavaScript

/**
* Patch: fix hardcoded defaultChecked handles in AP reader compose controller.
*
* Root cause:
* composeController() in compose.js sets target.defaultChecked using a
* hardcoded name comparison:
*
* target.defaultChecked = name === "@rick@rmendes.net" || name === "@rmendes.net";
*
* These are the original developer's handles and will never match any target
* on this installation. As a result, ALL syndication checkboxes in the AP
* reader compose form are rendered unchecked, so replies composed through the
* AP reader are never syndicated to ActivityPub.
*
* Fix:
* Replace the hardcoded comparison with `target.checked === true`.
* The Micropub config endpoint (q=config) already returns each syndicator's
* `checked` state. The AP syndicator has `checked: true` in indiekit.config.mjs,
* so the AP checkbox will be pre-checked by default, matching the same behaviour
* as the microsub reader compose form.
*/
import { access, readFile, writeFile } from "node:fs/promises";
const MARKER = "// [patch] ap-compose-default-checked";
const candidates = [
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/controllers/compose.js",
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/controllers/compose.js",
];
const OLD_SNIPPET = ` // Default-check only AP (Fedify) and Bluesky targets
// "@rick@rmendes.net" = AP Fedify, "@rmendes.net" = Bluesky
for (const target of syndicationTargets) {
const name = target.name || "";
target.defaultChecked = name === "@rick@rmendes.net" || name === "@rmendes.net";
}`;
const NEW_SNIPPET = ` // Pre-check syndication targets based on their configured checked state ${MARKER}
for (const target of syndicationTargets) { ${MARKER}
target.defaultChecked = target.checked === true; ${MARKER}
} ${MARKER}`;
async function exists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
let checked = 0;
let patched = 0;
for (const filePath of candidates) {
if (!(await exists(filePath))) continue;
checked += 1;
const source = await readFile(filePath, "utf8");
if (source.includes(MARKER)) {
console.log(`[postinstall] patch-ap-compose-default-checked: already applied to ${filePath}`);
continue;
}
if (!source.includes(OLD_SNIPPET)) {
console.warn(`[postinstall] patch-ap-compose-default-checked: target snippet not found in ${filePath}`);
continue;
}
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
if (updated === source) {
console.log(`[postinstall] patch-ap-compose-default-checked: no changes in ${filePath}`);
continue;
}
await writeFile(filePath, updated, "utf8");
patched += 1;
console.log(`[postinstall] Applied patch-ap-compose-default-checked to ${filePath}`);
}
if (checked === 0) {
console.log("[postinstall] patch-ap-compose-default-checked: no target files found");
} else if (patched === 0) {
console.log("[postinstall] patch-ap-compose-default-checked: already up to date");
} else {
console.log(`[postinstall] patch-ap-compose-default-checked: patched ${patched} file(s)`);
}