Archilyzer · Source

archilyzer

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

commit f64e77db6b491f26fa391a1c79fa20474d69a008
parent 1ecb8d06f703c0202b6dd4ff775d5f55469079f5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sun,  4 Oct 2026 16:13:50 -0400

records: fact-check chrome -- the README's `claim` and `render.chrome.factcheck` (the stamp, the tally, the schedule's `factcheck`, the stamp region), `qr.links`, `posts[].shot`; report-video.md's On-screen claim column; an [Unreleased] bullet

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/CHANGELOG.md | 1+
Mumtool/docs/report-video.md | 13+++++++++++++
Mumtool/report-to-video/README.md | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 86 insertions(+), 5 deletions(-)

diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] +- **A report cut can be a fact-check: each claim gets a verdict stamp over the footage, and a tally in the on-screen deck counts them.** Any clip, still or card entry of a report manifest can say which claim it is evidence for and the verdict, `"claim": { "id": "k3", "verdict": "CONTRADICTED" }` (one of CORROBORATED, PARTLY, CONTRADICTED, NOT_FOUND, UNTESTABLE). At the end of the last entry carrying each claim, the verdict slams in over the picture as a stamp in its colour and leaves with the transition, and a row of counts in the deck, one per verdict the cut uses, steps up as each stamp lands. `render.chrome.factcheck` sets each verdict's label and colour, how long the stamp is up (1–10 seconds, 3 by default) and which corner of the footage it sits in, and whether the tally is drawn beside the QR or before the title. A claim is a new key, separate from the clip review's `verdict`; one claim id given two verdicts, a claim on a teaser, or an unknown setting is refused with a sentence before a build fetches anything. umtool's On-screen table has a **claim** column (an id and a verdict per row), saved with the titles, and its preview shows the stamps and the tally before a build. The deck's QR can link each clip's original instead of the archive with `render.chrome.deck.qr.links: "original"` — a YouTube video at the clip's second, a Rumble page or an X post as they are; a clip's `citeUrl` still wins. A manifest post can carry `shot`, a screenshot (PNG, JPEG or WebP, beside the manifest), which its card draws in place of the post's text, its QR kept. A cut with none of these builds exactly as before. - **X posts are fetched more slowly, with random gaps.** Every read of X now waits a random 4 to 10 seconds before each request to X, where it used to page as fast as X answered, and always waits out a rate limit rather than pushing through. When fetching older posts, the pause between one three-month window and the next is a random 45 to 120 seconds instead of a fixed 15. A deep walk of an account's history takes longer; a routine fetch of new posts takes a few seconds more. - **The MCP's search tools take `date_from` and `date_to` as `2024-10-26` as well as `20241026`, and refuse a date they cannot read.** `search_transcripts` and `enumerate_matches` used to accept only `YYYYMMDD`: any other spelling was dropped with a footer warning and the search ran with no date bound, so a whole-corpus count could be read as the bounded one. Dashed, slashed and dotted dates and ISO timestamps are now normalised, and anything else is an error and nothing is searched. - **An X channel can fetch posts older than its timeline reaches.** X's timeline only pages back so far, so a fetch could end, and call the history done, well short of an account's first post. The new **Fetch older posts** button on an X channel's page (or `pnpm ops fetch-posts --json '{"slug":"<channel>","older":true}'`) walks back from the oldest archived post through X search, three months at a time, and saves posts the same way a normal fetch does; posts already archived are skipped. It needs a login, as search does: without one it stops at once and the channel shows **Needs credentials**. A run saves its place as it goes and stops after three hours; the next run continues from there. The walk ends at the account's creation date, after a year of windows with no posts, or at a date you give as `"floor": "YYYY-MM-DD"`, and the page's **Older posts** line then says it is complete; running it again says so and fetches nothing. A normal **Fetch posts** is unaffected and still fetches new posts from the top. Bluesky channels have no such button: their fetch already reads the whole history. diff --git a/umtool/docs/report-video.md b/umtool/docs/report-video.md @@ -336,6 +336,19 @@ DELETES the key — the same rule an empty attribution field follows. `updateClip` takes `onscreen` too, so a clip-bench save and an On-screen-table save are one rule, not two. +A row may also carry the entry's fact-check claim beside its text, +`{ title?, subtitle?, claim: { id, verdict } | null }` (`splitOnscreenRow`): +`claim` is set on the entry (never inside `onscreen`), `null` removes it, and +a row without the key leaves it as it is. After applying the batch the writer +runs `validateClaims()` over the whole timeline — one verdict per claim id, +none on a teaser — and a refusal writes nothing. The On-screen table shows a +**claim** column (an id and a verdict) per row, `GET /api/report/onscreen` +returns each row's `claim`, and PUT returns the `claims` it stored. The +preview applies a draft's claims too: the build's stamps are placed again over +its segments (`stampSchedule`), the deck's page draws the tally from them, and +the stamps' own composition (`chrome/stamp-preview/`, served under +`stamp-preview/`) is laid over the footage for the whole scrub. + `updateChrome(dir, chrome | null, { token })` writes `render.chrome` itself: `validateChrome()` first, so a block umtool saves is one the build accepts, and `null` removes the key — turning the deck off without touching anything diff --git a/umtool/report-to-video/README.md b/umtool/report-to-video/README.md @@ -236,7 +236,8 @@ whatever a manifest omits, one level deep: "pip": { "spacing": "even", "size": 6, "activeSize": 12 }, // spacing "even" | "time"; activeSize ≥ size "title": { "size": 54, "maxChars": 48 }, // size 24–96; maxChars 10–120 (the editor's counter, not a refusal) "subtitle": { "parts": "auto", "dateFormat": "long" }, // parts "auto" | a distinct list of channel/title/date/clock; dateFormat "long" | "iso" - "qr": { "show": true, "size": 150 }, // size 80–380, and at most height − 20 + "qr": { "show": true, "size": 150, // size 80–380, and at most height − 20 + "links": "site" }, // | "original": a clip's QR opens its record's own page (below) "overCards": "hide", // "hide" | "show" "motion": { "out": 0.3, "in": 0.45, "pip": 0.7 }, // seconds; out/in 0–2, pip 0–3 "posts": { "show": true, "layout": "popup", // | "feed": a column beside the footage for the whole cut (below) @@ -245,6 +246,11 @@ whatever a manifest omits, one level deep: "position": "top-right", // | "top-left" "width": 600, "qrSize": 120, "maxLines": 7, "inset": 24, // width 320–900 px; qrSize 80–200 and ≤ width/2; maxLines 2–14; inset 0–80 "links": "archive" } // | "original": where a post's QR goes (below) + }, + "factcheck": { // the fact-check stamps and tally (below); every key optional + "verdicts": { "PARTLY": { "label": "Partly true", "color": "#e3b23c" } }, // per verdict: label ≤ 24 characters, a #rgb/#rrggbb colour + "stamp": { "seconds": 3, "position": "top-left" }, // seconds 1–10; top-left | top-right | bottom-left | bottom-right | center + "tally": { "show": true, "position": "right" } // position "right" (beside the QR) | "left" (before the title) } } } @@ -275,6 +281,12 @@ card's `sub`. The QR follows a clip's corner-QR rule unchanged (`citeUrl`, else the site link at the clip's start); an image draws one only with an explicit `citeUrl`; a card never does. +`qr.links: "original"` points a clip's QR at its source instead of the archive: +the record's own `webpageUrl` — a YouTube page at the clip's start (`&t=<s>`, +or `?t=<s>` on a youtu.be link), a Rumble page as it is (it has no time +parameter a QR can carry), an X post's link as it is. A clip's `citeUrl` still +wins, and a clip whose record names no page keeps the site link. + ### `posts` — written statements on the deck A cut that wears the deck can carry a post — a Bluesky or X statement — as a @@ -293,7 +305,8 @@ footage, so it rides on a clip. "hide": false, "siteChannel": "piratesoftware-bsky", // optional: the archive channel that keeps it "siteUrl": null, // optional: an http(s) page the QR links instead - "postId": null } ] // optional: its id, when `url` does not carry one + "postId": null, // optional: its id, when `url` does not carry one + "shot": "shots/post-1.png" } ] // optional: a screenshot drawn instead of the text card ``` - **Which clip.** The one whose recording most closely PRECEDES the post: the @@ -333,6 +346,13 @@ footage, so it rides on a clip. column's side and its accent rim flares as it lands, then settles to a quiet glow; an accent rail runs down its leading edge and the platform is a pill beside the handle. The QR is fully opaque once the card is in. +- **A screenshot.** `shot` — a `.png`, `.jpg`, `.jpeg` or `.webp`, relative to + the manifest and inside its folder (no leading `/`, no `..`) — is drawn in + place of the date, handle and words, as wide as they would be and no taller + than a full card of them; the QR cell stays. The popup and the feed both + draw it, the page waits for the picture before it measures the stack, and a + shot that cannot be read refuses that region's compose. `text` is still + required: it is the post's words wherever they are listed. #### Where a post's QR links @@ -382,6 +402,51 @@ deck build refuses a bad `posts` before a single fetch. Posts are drawn only under the deck: without `render.chrome` they are data for the report, and nothing in a build reads them. +### `claim` and `render.chrome.factcheck` — fact-check stamps and the tally + +A cut that wears the deck can be a fact-check: any `clip`, `image` or card +entry may say which claim it is evidence for, and the claim's verdict. + +```jsonc +{ "type": "clip", "id": "c07", …, + "claim": { "id": "k3", "verdict": "CONTRADICTED" } } // CORROBORATED | PARTLY | CONTRADICTED | NOT_FOUND | UNTESTABLE +``` + +`claim` is its own key: an entry's `verdict` is the clip review's +(`confirmed` / `incorrect`) and is untouched. The id is letters, digits and +`_ . : -` (≤ 64); a claim id carries ONE verdict across the timeline, a teaser +carries none, and `validateClaims()` (`factcheck.mjs`) refuses either before a +build fetches — umtool's writer refuses with the same function. + +- **The stamp.** Each claim is stamped once, on the LAST segment that carries + it (its evidence may run over several clips): the verdict's label, in its + colour, slams in over the footage `stamp.seconds` (3) before that segment's + outgoing transition — or as its incoming dissolve ends, on a shorter one — + flashes as it lands, and leaves with the transition, as a popup post does. + It sits in a 560×200 box (at 1920 wide, scaled with the frame) `28` px in + from the footage box's `stamp.position` corner (`top-left` by default, the + corner the popup posts do not use), the feed's footage box in a feed cut. +- **The tally.** A cell per verdict the cut stamps, in the deck beside the QR's + cell (`tally.position: "left"`: before the title), each a count over its + label. A count steps up — the old number rolls out, the new one in, the cell + flashes — at the moment its stamp lands; a cell is dim until its first. It + is part of the deck's panel, so it slides away over a card with it, and the + title's column gives it its room only in a cut that stamps something. +- **The schedule.** A stamped segment carries `claim`, and `schedule.json` + gains `factcheck: { stamps: [{ claim, verdict, segment, at, landed, out }] }` + (`stampSchedule`) — only when a claim is stamped, so a cut without one + writes the schedule it always did. +- **The render.** `chrome-stamp.mjs` is the stamps' page (pure, like the + deck's), `compose-chrome.mjs --region stamp` composes and renders it for the + whole cut (`chrome/stamp/`, `chrome/stamp-frames/`, cached by its key, a + window `stamp-from<s>`), and the build lays it last, over the posts, exactly + as it lays the deck's. `verify-build.mjs` checks its frame count. + +`render.chrome.factcheck` sets the verdicts' labels and colours (one at a time: +a verdict named without a colour keeps the default's), the stamp's seconds and +corner, and whether and where the tally is drawn; `validateChrome()` refuses an +unknown key there as everywhere in the block. + ### The `image` entry type A still: the receipts a clip cannot say out loud — a post, a thread, a DM, a @@ -1222,10 +1287,11 @@ implementation of the arithmetic to drift. `compose-chrome.mjs --region deck` is `--region chart`'s sibling: the same CLI, the same render cache, the same vendored GSAP, a different composition -(`chrome-deck.mjs` instead of the chart band's SVG). +(`chrome-deck.mjs` instead of the chart band's SVG). `--region feed`, `stamp`, +`posts` and `teaser` are the deck's other regions (below). ``` -node umtool/report-to-video/compose-chrome.mjs <manifest.json> [--region chart|deck] [--variant sourced|full] +node umtool/report-to-video/compose-chrome.mjs <manifest.json> [--region chart|deck|feed|stamp|posts|teaser] [--variant sourced|full] [--from <s>] [--duration <s>] [--out <dir>] [--preview] [--still <s> --png <path>] [--render] [--workers 4] [--quality high] [--format png-sequence] [--fps 30] @@ -1489,7 +1555,8 @@ substitution happened. ### `verify-build.mjs`'s deck checks, and their limit Confirms `schedule.json` is a *measured* (not estimated) deck schedule, that -`chrome/deck-frames` holds exactly `frameCount(schedule.total, fps)` frames, +`chrome/deck-frames` (and `chrome/feed-frames` / `chrome/stamp-frames` when the +schedule has a feed or stamps) holds exactly `frameCount(schedule.total, fps)` frames, and that the finished file's video stream is that many frames long — laid with `shortest=1`, so a short sequence shortens the cut silently rather than erroring. It **cannot tell whether the overlay was actually laid onto the