Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

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:
Acommon/components/report/exportSlides.test.ts | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/controller/fetchOlderPosts.test.ts | 8++++----
Dcommon/lib/report/exportSlides.test.ts | 146-------------------------------------------------------------------------------
Mcommon/social/xGalleryDlFetcher.ts | 2+-
Meditor/e2e/fixtures/bin/fake-gallery-dl.mjs | 4++--
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 &lt;fact-check&gt;/); +}); + +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 &lt;fact-check&gt;/); -}); - -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]);