commit 59b7d7bcaf091d80aedbbbebc95568a5c4021f70
parent ec7018490b752bfb2696e9c93e5dff560cb97644
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 9 Oct 2026 20:31:19 -0400
r19 gates: the X fakes read --post-range; the slides parity test lives beside Slide.tsx
fetchOlderPosts' inline fake and the editor e2e fake-gallery-dl still capped on
--range after ec701849, so the older-walk limit test wrote 2, not 1. The
exportSlides test imported components/ from lib/, a new back-edge the
architecture guard refuses; it moves to components/report/.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
5 files changed, 153 insertions(+), 153 deletions(-)
diff --git a/common/components/report/exportSlides.test.ts b/common/components/report/exportSlides.test.ts
@@ -0,0 +1,146 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import * as React from "react";
+import { renderToStaticMarkup } from "react-dom/server";
+import type { Report } from "../../lib/report/schema";
+import { validateReport } from "../../lib/report/validate";
+import { buildReportPageView, type ReportPageView } from "../../lib/report/views";
+import { buildReportSlides } from "../../lib/report/slides";
+import { reportSlidesHtml, slidesImagePaths, SLIDES_EXPORT_CSS } from "../../lib/report/exportSlidesHtml";
+import { Slide } from "./slides/Slide";
+
+(globalThis as { React?: typeof React }).React = React;
+
+const HERE = path.dirname(fileURLToPath(import.meta.url));
+const CSS = readFileSync(path.join(HERE, "..", "..", "styles", "slides.css"), "utf8");
+
+function report(): Report {
+ return {
+ format: "archilyzer-report",
+ version: 1,
+ id: "demo",
+ kind: "factcheck",
+ series: "Demo Checks",
+ title: "A demo <fact-check>",
+ summary: "One thing [right](cite:v1).",
+ published: "2026-10-01",
+ subject: { source: "s0" },
+ sources: { s0: { kind: "article", title: "An article", accent: "#8a6fb0" } },
+ citations: {
+ v1: { kind: "video", channel: "demo-channel", id: "abc123", start: 10, end: 20, quote: "one" },
+ p1: { kind: "post", channel: "demo-social", id: "123", quote: "three" },
+ a1: { kind: "source", source: "s0", quote: "the claim", image: "stills/a1.png" },
+ },
+ slides: { closing: "The end." },
+ entries: [{ id: "e1", date: "2026-10-03T08:00:00Z", title: "An update", body: "It [moved](cite:v1). Then more. And more." }],
+ sections: [
+ {
+ id: "ch1",
+ title: "Chapter one",
+ body: "Opening. Second.",
+ slide: { points: ["A point [citing](cite:v1)"], cite: "a1" },
+ claims: [
+ { id: "k1", text: "Claim one.", verdict: "CONTRADICTED", gist: "No.", sourceQuote: { citation: "a1" }, citations: ["a1", "v1"] },
+ { id: "k2", text: "Claim two.", verdict: "PARTLY", citations: ["p1"], slide: { layout: "quote" } },
+ { id: "k3", text: "Claim three.", flag: "No source given" },
+ ],
+ },
+ ],
+ };
+}
+
+function view(r = report()): ReportPageView {
+ assert.deepEqual(validateReport(r), []);
+ return buildReportPageView(r, {
+ record: (c) => ({ channel: c.channel, id: c.id, title: `Record ${c.id}` }),
+ poster: () => "/media/posters/abc123.png",
+ post: () => ({ author: "@demo", shot: "/media/posts/demo-social/123/shot.png" }),
+ });
+}
+
+const FOOTER = { revision: 2, date: "2026-10-04", reportSha256: "ab".repeat(32) };
+const images: Record<string, string> = {
+ "/media/posters/abc123.png": "data:image/png;base64,AAA",
+ "/reports/demo/stills/a1.png": "data:image/png;base64,BBB",
+ "/media/posts/demo-social/123/shot.png": "data:image/png;base64,CCC",
+};
+const html = (v = view(), siteUrl = "https://site.example/") =>
+ reportSlidesHtml(v, { css: CSS, accent: "#6a4bc4", siteUrl, footer: FOOTER, image: (p) => images[p] });
+
+test("one page per slide, numbered as the site's hash numbers them, in the slides' order", () => {
+ const v = view();
+ const slides = buildReportSlides(v);
+ const out = html(v);
+ const pages = [...out.matchAll(/<section class="rs-page" id="s-(\d+)"/g)].map((m) => Number(m[1]));
+ assert.deepEqual(pages, slides.map((s) => s.n));
+ const keys = [...out.matchAll(/<article class="rs-slide" data-slide="([^"]+)"/g)].map((m) => m[1]);
+ assert.deepEqual(keys, slides.map((s) => s.key));
+});
+
+test("one file: the stylesheet and the script inline, every picture the resolver's, nothing fetched", () => {
+ const out = html();
+ assert.ok(out.includes(CSS.slice(0, 200)));
+ assert.ok(out.includes(SLIDES_EXPORT_CSS.slice(0, 80)));
+ assert.equal([...out.matchAll(/<script>/g)].length, 1);
+ assert.doesNotMatch(out, /<script [^>]*src=|<link /);
+ const srcs = [...out.matchAll(/<img [^>]*src="([^"]+)"/g)].map((m) => m[1]);
+ // The clip's poster on k1's evidence, the still on the section's; k2's
+ // quote shows its post's words, not its capture.
+ assert.deepEqual(srcs.sort(), ["data:image/png;base64,AAA", "data:image/png;base64,BBB"]);
+ assert.deepEqual(slidesImagePaths(view()), ["/media/posters/abc123.png", "/reports/demo/stills/a1.png"]);
+ // The site's accent, on the light base.
+ assert.match(out, /<html lang="en" class="rs-root" style="--brand:#6a4bc4;/);
+ // Escaped, as everywhere.
+ assert.match(out, /A demo <fact-check>/);
+});
+
+test("the way back: each slide links to its place in the article on the site; with no site, nowhere", () => {
+ const out = html();
+ assert.match(out, /href="https:\/\/site\.example\/reports\/demo\/#k1" data-slide-read="k1"/);
+ assert.match(out, /href="https:\/\/site\.example\/reports\/demo\/#report-end" data-slide-read="report-end"/);
+ assert.doesNotMatch(html(view(), ""), /data-slide-read=/);
+ // A citation's number goes to the reference list under the deck, which links out.
+ assert.match(out, /<a href="#c-v1">\[1\]<\/a>/);
+ assert.match(out, /<li id="c-v1" value="1">/);
+ assert.match(out, /href="https:\/\/site\.example\/m\/demo-channel\/abc123\/10\.00-20\.00\/"/);
+});
+
+test("a timeline entry is a slide: its date, its title, its first sentences", () => {
+ const out = html();
+ assert.match(
+ out,
+ /data-slide="entry:e1" data-slide-kind="entry" data-layout="statement"[^>]*>.*<b>Timeline<\/b> · 1 of 1.*<p class="rs-dates"><time datetime="2026-10-03T08:00:00Z">2026-10-03<\/time><\/p><h2 class="rs-h">An update<\/h2><div class="rs-statement"><div class="rs-md"><p>It moved<sup[^>]*><a href="#c-v1">\[1\]<\/a><\/sup>\. Then more\.<\/p>/s,
+ );
+ assert.match(out, /href="https:\/\/site\.example\/reports\/demo\/#e1" data-slide-read="e1"/);
+});
+
+test("the sources slide names which document this is", () => {
+ assert.match(html(), /data-export-footer="">Revision 2 · 2026-10-04 · report sha256 abababababab</);
+ assert.match(html(), /The end\./);
+});
+
+test("the string slides mirror Slide.tsx: the same `rs-` classes, slide by slide", () => {
+ const v = view();
+ const slides = buildReportSlides(v);
+ // The export's own line on the sources slide (which document this is) is
+ // the one difference.
+ const out = html(v).replace(/<p class="rs-dates" data-export-footer="">[^<]*<\/p>/, "");
+ const classes = (markup: string) =>
+ [...new Set([...markup.matchAll(/class="([^"]*)"/g)].flatMap((m) => m[1].split(/\s+/)).filter((c) => c.startsWith("rs-") && c !== "rs-n"))].sort();
+ const articles = out.split(/(?=<article class="rs-slide")/).slice(1).map((a) => a.slice(0, a.indexOf("</article>") + 10));
+ slides.forEach((s, i) => {
+ const react = renderToStaticMarkup(
+ React.createElement(Slide, { slide: s, view: v, total: slides.length, pageUrl: "https://site.example/reports/demo/" }),
+ );
+ assert.deepEqual(classes(articles[i]), classes(react), s.key);
+ });
+});
+
+test("every `rs-` class a slide uses has a rule in slides.css", () => {
+ const used = new Set([...html().matchAll(/class="([^"]*)"/g)].flatMap((m) => m[1].split(/\s+/)).filter((c) => c.startsWith("rs-")));
+ const styled = new Set([...(CSS + SLIDES_EXPORT_CSS).matchAll(/\.(rs-[a-z-]+)/g)].map((m) => m[1]));
+ assert.deepEqual([...used].filter((c) => !styled.has(c)), []);
+});
diff --git a/common/controller/fetchOlderPosts.test.ts b/common/controller/fetchOlderPosts.test.ts
@@ -34,7 +34,7 @@ const POST_DATES = [
// The fake: a timeline URL reads nothing (the timeline is exhausted); a search
// URL answers its query's from:/since:/until:/max_id: from POST_DATES, newest
-// first, honouring --range. FAKE_ACCOUNT_DATE is the account's creation date
+// first, honouring --post-range. FAKE_ACCOUNT_DATE is the account's creation date
// carried on each record's `user`. FAKE_MODE=stall prints the window's first
// post, then sleeps on a "rate limit" until killed.
await writeFile(
@@ -48,8 +48,8 @@ if (!url.pathname.startsWith("/search")) process.exit(0);
const q = url.searchParams.get("q");
const term = (k) => (q.split(" ").find((t) => t.startsWith(k + ":")) || "").slice(k.length + 1) || undefined;
const from = term("from"), since = term("since"), until = term("until"), maxId = term("max_id");
-const range = args[args.indexOf("--range") + 1];
-const cap = args.includes("--range") ? Number(range.split("-")[1]) : Infinity;
+const range = args[args.indexOf("--post-range") + 1];
+const cap = args.includes("--post-range") ? Number(range.split("-")[1]) : Infinity;
const dates = ${JSON.stringify(POST_DATES)};
const idOf = (d) => (BigInt(Date.parse(d.replace(" ", "T") + "Z") - 1288834974657) << 22n);
const hits = dates
@@ -357,7 +357,7 @@ test("a limit stops the walk part-way with a resume point", async () => {
assert.equal(result.complete, false);
assert.equal(result.written, 1);
const argv = (await argvLines()).at(-1)!;
- assert.equal(argv[argv.indexOf("--range") + 1], "1-2");
+ assert.equal(argv[argv.indexOf("--post-range") + 1], "1-2");
const older = (await readPostFetchState(channelRoot))?.older;
assert.equal(older?.maxId, idOf(POST_DATES[1]));
assert.equal(older?.complete, undefined);
diff --git a/common/lib/report/exportSlides.test.ts b/common/lib/report/exportSlides.test.ts
@@ -1,146 +0,0 @@
-import { test } from "node:test";
-import assert from "node:assert/strict";
-import { readFileSync } from "node:fs";
-import path from "node:path";
-import { fileURLToPath } from "node:url";
-import * as React from "react";
-import { renderToStaticMarkup } from "react-dom/server";
-import type { Report } from "./schema";
-import { validateReport } from "./validate";
-import { buildReportPageView, type ReportPageView } from "./views";
-import { buildReportSlides } from "./slides";
-import { reportSlidesHtml, slidesImagePaths, SLIDES_EXPORT_CSS } from "./exportSlidesHtml";
-import { Slide } from "../../components/report/slides/Slide";
-
-(globalThis as { React?: typeof React }).React = React;
-
-const HERE = path.dirname(fileURLToPath(import.meta.url));
-const CSS = readFileSync(path.join(HERE, "..", "..", "styles", "slides.css"), "utf8");
-
-function report(): Report {
- return {
- format: "archilyzer-report",
- version: 1,
- id: "demo",
- kind: "factcheck",
- series: "Demo Checks",
- title: "A demo <fact-check>",
- summary: "One thing [right](cite:v1).",
- published: "2026-10-01",
- subject: { source: "s0" },
- sources: { s0: { kind: "article", title: "An article", accent: "#8a6fb0" } },
- citations: {
- v1: { kind: "video", channel: "demo-channel", id: "abc123", start: 10, end: 20, quote: "one" },
- p1: { kind: "post", channel: "demo-social", id: "123", quote: "three" },
- a1: { kind: "source", source: "s0", quote: "the claim", image: "stills/a1.png" },
- },
- slides: { closing: "The end." },
- entries: [{ id: "e1", date: "2026-10-03T08:00:00Z", title: "An update", body: "It [moved](cite:v1). Then more. And more." }],
- sections: [
- {
- id: "ch1",
- title: "Chapter one",
- body: "Opening. Second.",
- slide: { points: ["A point [citing](cite:v1)"], cite: "a1" },
- claims: [
- { id: "k1", text: "Claim one.", verdict: "CONTRADICTED", gist: "No.", sourceQuote: { citation: "a1" }, citations: ["a1", "v1"] },
- { id: "k2", text: "Claim two.", verdict: "PARTLY", citations: ["p1"], slide: { layout: "quote" } },
- { id: "k3", text: "Claim three.", flag: "No source given" },
- ],
- },
- ],
- };
-}
-
-function view(r = report()): ReportPageView {
- assert.deepEqual(validateReport(r), []);
- return buildReportPageView(r, {
- record: (c) => ({ channel: c.channel, id: c.id, title: `Record ${c.id}` }),
- poster: () => "/media/posters/abc123.png",
- post: () => ({ author: "@demo", shot: "/media/posts/demo-social/123/shot.png" }),
- });
-}
-
-const FOOTER = { revision: 2, date: "2026-10-04", reportSha256: "ab".repeat(32) };
-const images: Record<string, string> = {
- "/media/posters/abc123.png": "data:image/png;base64,AAA",
- "/reports/demo/stills/a1.png": "data:image/png;base64,BBB",
- "/media/posts/demo-social/123/shot.png": "data:image/png;base64,CCC",
-};
-const html = (v = view(), siteUrl = "https://site.example/") =>
- reportSlidesHtml(v, { css: CSS, accent: "#6a4bc4", siteUrl, footer: FOOTER, image: (p) => images[p] });
-
-test("one page per slide, numbered as the site's hash numbers them, in the slides' order", () => {
- const v = view();
- const slides = buildReportSlides(v);
- const out = html(v);
- const pages = [...out.matchAll(/<section class="rs-page" id="s-(\d+)"/g)].map((m) => Number(m[1]));
- assert.deepEqual(pages, slides.map((s) => s.n));
- const keys = [...out.matchAll(/<article class="rs-slide" data-slide="([^"]+)"/g)].map((m) => m[1]);
- assert.deepEqual(keys, slides.map((s) => s.key));
-});
-
-test("one file: the stylesheet and the script inline, every picture the resolver's, nothing fetched", () => {
- const out = html();
- assert.ok(out.includes(CSS.slice(0, 200)));
- assert.ok(out.includes(SLIDES_EXPORT_CSS.slice(0, 80)));
- assert.equal([...out.matchAll(/<script>/g)].length, 1);
- assert.doesNotMatch(out, /<script [^>]*src=|<link /);
- const srcs = [...out.matchAll(/<img [^>]*src="([^"]+)"/g)].map((m) => m[1]);
- // The clip's poster on k1's evidence, the still on the section's; k2's
- // quote shows its post's words, not its capture.
- assert.deepEqual(srcs.sort(), ["data:image/png;base64,AAA", "data:image/png;base64,BBB"]);
- assert.deepEqual(slidesImagePaths(view()), ["/media/posters/abc123.png", "/reports/demo/stills/a1.png"]);
- // The site's accent, on the light base.
- assert.match(out, /<html lang="en" class="rs-root" style="--brand:#6a4bc4;/);
- // Escaped, as everywhere.
- assert.match(out, /A demo <fact-check>/);
-});
-
-test("the way back: each slide links to its place in the article on the site; with no site, nowhere", () => {
- const out = html();
- assert.match(out, /href="https:\/\/site\.example\/reports\/demo\/#k1" data-slide-read="k1"/);
- assert.match(out, /href="https:\/\/site\.example\/reports\/demo\/#report-end" data-slide-read="report-end"/);
- assert.doesNotMatch(html(view(), ""), /data-slide-read=/);
- // A citation's number goes to the reference list under the deck, which links out.
- assert.match(out, /<a href="#c-v1">\[1\]<\/a>/);
- assert.match(out, /<li id="c-v1" value="1">/);
- assert.match(out, /href="https:\/\/site\.example\/m\/demo-channel\/abc123\/10\.00-20\.00\/"/);
-});
-
-test("a timeline entry is a slide: its date, its title, its first sentences", () => {
- const out = html();
- assert.match(
- out,
- /data-slide="entry:e1" data-slide-kind="entry" data-layout="statement"[^>]*>.*<b>Timeline<\/b> · 1 of 1.*<p class="rs-dates"><time datetime="2026-10-03T08:00:00Z">2026-10-03<\/time><\/p><h2 class="rs-h">An update<\/h2><div class="rs-statement"><div class="rs-md"><p>It moved<sup[^>]*><a href="#c-v1">\[1\]<\/a><\/sup>\. Then more\.<\/p>/s,
- );
- assert.match(out, /href="https:\/\/site\.example\/reports\/demo\/#e1" data-slide-read="e1"/);
-});
-
-test("the sources slide names which document this is", () => {
- assert.match(html(), /data-export-footer="">Revision 2 · 2026-10-04 · report sha256 abababababab</);
- assert.match(html(), /The end\./);
-});
-
-test("the string slides mirror Slide.tsx: the same `rs-` classes, slide by slide", () => {
- const v = view();
- const slides = buildReportSlides(v);
- // The export's own line on the sources slide (which document this is) is
- // the one difference.
- const out = html(v).replace(/<p class="rs-dates" data-export-footer="">[^<]*<\/p>/, "");
- const classes = (markup: string) =>
- [...new Set([...markup.matchAll(/class="([^"]*)"/g)].flatMap((m) => m[1].split(/\s+/)).filter((c) => c.startsWith("rs-") && c !== "rs-n"))].sort();
- const articles = out.split(/(?=<article class="rs-slide")/).slice(1).map((a) => a.slice(0, a.indexOf("</article>") + 10));
- slides.forEach((s, i) => {
- const react = renderToStaticMarkup(
- React.createElement(Slide, { slide: s, view: v, total: slides.length, pageUrl: "https://site.example/reports/demo/" }),
- );
- assert.deepEqual(classes(articles[i]), classes(react), s.key);
- });
-});
-
-test("every `rs-` class a slide uses has a rule in slides.css", () => {
- const used = new Set([...html().matchAll(/class="([^"]*)"/g)].flatMap((m) => m[1].split(/\s+/)).filter((c) => c.startsWith("rs-")));
- const styled = new Set([...(CSS + SLIDES_EXPORT_CSS).matchAll(/\.(rs-[a-z-]+)/g)].map((m) => m[1]));
- assert.deepEqual([...used].filter((c) => !styled.has(c)), []);
-});
diff --git a/common/social/xGalleryDlFetcher.ts b/common/social/xGalleryDlFetcher.ts
@@ -1334,7 +1334,7 @@ export const xGalleryDlFetcher: SocialFetcher = {
}
// A clean exit walked everything it was given: gallery-dl waits out rate
- // limits rather than skipping pages. Only a --range cap leaves history
+ // limits rather than skipping pages. Only a --post-range cap leaves history
// behind — resume from the last page it finished.
const capped = Boolean(limit && stream.distinct >= limit);
return capped
diff --git a/editor/e2e/fixtures/bin/fake-gallery-dl.mjs b/editor/e2e/fixtures/bin/fake-gallery-dl.mjs
@@ -21,10 +21,10 @@ const url = args[args.length - 1] ?? "";
const handleMatch = /(?:x|twitter)\.com\/([^/?#]+)/i.exec(url);
const handle = handleMatch ? handleMatch[1] : "faketester";
-// `--range 1-N` caps how many items the real binary emits; honour it so the
+// `--post-range 1-N` caps how many posts the real binary emits; honour it so the
// limit path is exercised.
let limit = Infinity;
-const rangeIdx = args.indexOf("--range");
+const rangeIdx = args.indexOf("--post-range");
if (rangeIdx >= 0 && args[rangeIdx + 1]) {
const m = /^1-(\d+)$/.exec(args[rangeIdx + 1]);
if (m) limit = Number(m[1]);