Archilyzer · Source

archilyzer

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

commit 192a13e1aff217f861194186ad7f3e3795553bef
parent 509770004976fe4fd124e5a5e35d28ca2cb5ec00
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri,  9 Oct 2026 17:53:36 -0400

export: a report's Timeline, and Subscribe: RSS · JSON when it has feeds

The Timeline sits after In brief, before what the check found: dated blocks
newest first on a rail, the date first and in the accent, linking to the
entry's own anchor. The header links the feeds and the page's metadata names
them (<link rel="alternate">, absolute). A moment cited in an entry says so
under "Cited in".

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

Diffstat:
Mexport/app/components/reports/MomentArticle.tsx | 10++++++++--
Mexport/app/components/reports/ReportArticle.tsx | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mexport/app/lib/reports.ts | 21+++++++++++++++++++--
3 files changed, 91 insertions(+), 6 deletions(-)

diff --git a/export/app/components/reports/MomentArticle.tsx b/export/app/components/reports/MomentArticle.tsx @@ -150,7 +150,7 @@ export default function MomentArticle({ view }: { view: MomentPageView }) { <ul data-cited-in="" className="flex flex-col gap-2"> {view.citedIn.map((e) => ( <li - key={`${e.reportId}/${e.sectionId}/${e.claimId}/${e.citationId}`} + key={`${e.reportId}/${e.sectionId}/${e.claimId}/${e.entryId ?? ""}/${e.citationId}`} className="flex flex-col gap-1 rounded-lg border border-border bg-card p-3 text-sm" > <a href={e.href} className={`font-medium ${textLink}`}> @@ -158,7 +158,13 @@ export default function MomentArticle({ view }: { view: MomentPageView }) { </a> <div className="flex flex-wrap items-center gap-2 text-muted-foreground"> {e.verdict && e.verdictStyle && <VerdictChip verdict={e.verdict} style={e.verdictStyle} />} - {e.sectionTitle ? <span>{e.sectionTitle}</span> : !e.sectionId && <span>Summary</span>} + {e.sectionTitle ? ( + <span>{e.sectionTitle}</span> + ) : e.entryId ? ( + <span>Timeline{e.entryTitle ? ` — ${e.entryTitle}` : ""}</span> + ) : ( + !e.sectionId && <span>Summary</span> + )} {(e.claimTitle ?? e.claimText) && ( <span className="text-foreground">— {e.claimTitle ?? e.claimText}</span> )} diff --git a/export/app/components/reports/ReportArticle.tsx b/export/app/components/reports/ReportArticle.tsx @@ -1,4 +1,4 @@ -import { ArrowDownToLine } from "lucide-react"; +import { ArrowDownToLine, Rss } from "lucide-react"; import { AddedMark, CitationCard } from "yt-dlp-transcript-common/components/citations/CitationCard"; import { CitationsProvider } from "yt-dlp-transcript-common/components/citations/CitationsContext"; import { CitedMarkdown } from "yt-dlp-transcript-common/components/citations/CitedMarkdown"; @@ -9,6 +9,7 @@ import { REPORT_PAGE_ANCHOR_IDS } from "yt-dlp-transcript-common/lib/report/slid import { hasSlides } from "yt-dlp-transcript-common/lib/report/slides"; import type { SourceArchive } from "yt-dlp-transcript-common/lib/citations/schema"; import { + entryDateLabel, foundGroups, orderedCitations, reportDateParts, @@ -18,6 +19,7 @@ import { verdictTally, type CitationView, type ClaimView, + type EntryView, type ReportPageView, type SourceCitationView, } from "yt-dlp-transcript-common/lib/report/views"; @@ -28,7 +30,8 @@ import { ArchiveList, ReportName, SourceBlock, SubjectCard, textLink } from "./p // revision, linked to its history; the document under review as a card on its // colour's rail, with its archive links; the subtitle), the three tiers each // opened by a depth marker — the quick take (a fact-check's tally, the -// summary), what the check found, and every claim with its evidence: each +// summary), the timeline when the report keeps one (dated entries, newest +// first, each its own anchor), what the check found, and every claim with its evidence: each // claim its verdict and flag, the document's own sentence (its still), the // findings with their inline citations, and the evidence cards (what the // report added first, marked; what the document gave itself folded away @@ -36,6 +39,10 @@ import { ArchiveList, ReportName, SourceBlock, SubjectCard, textLink } from "./p // the downloads: the report as files (HTML, PDF, Markdown, the evidence pack) // and its citations as data. // +// A report with a timeline on a site with a public URL publishes feeds of it +// (common/lib/report/feeds.ts): the header links them ("Subscribe"), and the +// page's metadata names them (lib/reports.ts reportMetadata). +// // ONE ARTICLE, THREE SHAPES (release 22): the header carries the view switch // (Article | Slides | Overview — common/components/report/slides/ReportReader), // and every place a slide stands for has its id here: the header, the quick @@ -144,6 +151,34 @@ function TierMarker({ depth }: { depth: 1 | 2 | 3 }) { ); } +// A timeline entry: its date first and largest (what a timeline is read by), +// linking to its own anchor, then its title and its body. A dot on the +// timeline's rail marks it. +function Entry({ entry }: { entry: EntryView }) { + return ( + <li id={entry.id} data-entry={entry.id} className="relative flex scroll-mt-20 flex-col gap-1.5"> + <span + aria-hidden="true" + className="absolute top-[0.45rem] -left-[calc(1rem+4.5px)] size-2 rounded-full bg-brand ring-4 ring-background sm:-left-[calc(1.25rem+4.5px)]" + /> + <p className="flex flex-wrap items-baseline gap-x-2 font-mono text-sm"> + <a href={`#${entry.id}`} data-entry-date="" className="font-semibold text-brand hover:underline"> + <time dateTime={entry.date}>{entryDateLabel(entry.date)}</time> + </a> + {entry.updated && ( + <span className="text-xs text-muted-foreground"> + updated <time dateTime={entry.updated}>{entryDateLabel(entry.updated)}</time> + </span> + )} + </p> + <h3 className="font-display text-lg font-semibold leading-snug text-foreground">{entry.title}</h3> + <CitedMarkdown className={proseClass}> + {entry.body} + </CitedMarkdown> + </li> + ); +} + // A claim: its verdict, flag and title; the document's own sentence (its // still, else its verbatim words) when the claim cites one, else the claim as // the report states it, plain; the findings; the evidence. @@ -278,6 +313,19 @@ export default function ReportArticle({ view }: { view: ReportPageView }) { )} </p> )} + {view.feeds && ( + <p data-report-feeds="" className="flex items-center gap-1.5 font-mono text-xs text-muted-foreground"> + <Rss className="size-3.5" aria-hidden /> + Subscribe: + <a href={view.feeds.rss} type="application/rss+xml" data-feed="rss" className={textLink}> + RSS + </a> + <span aria-hidden="true">·</span> + <a href={view.feeds.json} type="application/feed+json" data-feed="json" className={textLink}> + JSON + </a> + </p> + )} {hasSlides(view) && ( <div data-report-views="" className="flex flex-wrap items-center gap-3"> <ReportReader pagePath={reportViewPath(view.id)} title={view.title} series={view.series} /> @@ -337,6 +385,20 @@ export default function ReportArticle({ view }: { view: ReportPageView }) { </p> </section> + {/* The timeline: dated entries, newest first, on a rail. */} + {view.entries && view.entries.length > 0 && ( + <section data-report-timeline="" aria-labelledby="timeline-heading" className="flex flex-col gap-4"> + <h2 id="timeline-heading" className="font-display text-2xl font-semibold tracking-tight text-foreground"> + Timeline + </h2> + <ol className="ml-1 flex flex-col gap-7 border-l border-border pl-4 sm:pl-5"> + {view.entries.map((e) => ( + <Entry key={e.id} entry={e} /> + ))} + </ol> + </section> + )} + {/* What the check found: every ruled claim by verdict, one line each. */} {groups.length > 0 && ( <section id="found" data-report-tier="2" aria-labelledby="found-heading" className="flex scroll-mt-20 flex-col gap-4"> diff --git a/export/app/lib/reports.ts b/export/app/lib/reports.ts @@ -16,6 +16,7 @@ import { REPORT_INDEX_FORMAT, REPORT_PAGE_FORMAT, REPORTS_INDEX_PATH, + REPORT_FEED_MIME, momentViewPath, reportFullTitle, reportViewPath, @@ -83,9 +84,25 @@ export function onlyReportView(): ReportPageView | null { // A report's page metadata — its page's, and a one-report cited site's home's, // so the tab reads the same on both: the title as one line names it // (`<series>: <title>`, the layout's template adds the site), the subtitle as -// the description. +// the description, and its timeline's feeds when it publishes them (a +// `<link rel="alternate">` each, by absolute URL: the layout sets no +// metadataBase). export function reportMetadata(view: ReportPageView): Metadata { - return { title: reportFullTitle(view), ...(view.subtitle ? { description: view.subtitle } : {}) }; + const title = reportFullTitle(view); + return { + title, + ...(view.subtitle ? { description: view.subtitle } : {}), + ...(view.feeds + ? { + alternates: { + types: { + [REPORT_FEED_MIME.rss]: [{ url: view.feeds.rss, title }], + [REPORT_FEED_MIME.json]: [{ url: view.feeds.json, title }], + }, + }, + } + : {}), + }; } // The moment keys to build pages for (only keys exactly as momentKey spells