Archilyzer · Source

archilyzer

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

commit 5064c1156bb25260e1cb70d39a5ca934d0c90387
parent 3ceadf7606a69ca9b95e9a7a00622c2344e3bce9
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 25 Sep 2026 18:47:40 -0400

homepage: official.transcripts counted where transcripts are placed; gone only when held

Release 8 review of slice H, the two LOWs and three NITs:
- official.transcripts is a per-site accumulator from the same pass that
  places or leaves unplaced each transcript, so placed + unplaced =
  official.transcripts holds for a transcribedDate bucketize drops;
- a site the activity filter dropped gains no monthly key;
- gone counts a deleted record only with a downloadedDate ("still here");
- FamilyStats uses lg:grid-cols-5 when there is no gone cell;
- stale comments in ArchiveGrowthChart.tsx and page.tsx, and the test title.
homepageSummary.test.ts +3. No number on today's data moves: all 480
deleted records have a downloadedDate, and no transcribedDate is future.

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

Diffstat:
Mcommon/lib/homepageSummary.test.ts | 42+++++++++++++++++++++++++++++++++++++++++-
Mcommon/lib/homepageSummary.ts | 34++++++++++++++++++++++++++++------
Mhomepage/app/components/ArchiveGrowthChart.tsx | 3++-
Mhomepage/app/components/FamilyStats.tsx | 9++++++++-
Mhomepage/app/page.tsx | 4++--
5 files changed, 81 insertions(+), 11 deletions(-)

diff --git a/common/lib/homepageSummary.test.ts b/common/lib/homepageSummary.test.ts @@ -80,7 +80,7 @@ const STATS: VideoStat[] = [ stat({ channelSlug: "p1", id: "z1", uploadDate: "20200101", status: "deleted" }), ]; -test("monthly: upload months, zero-filled from first upload to the build month, every site keyed", () => { +test("monthly: upload months, zero-filled from first upload to the last complete month, every site keyed", () => { const s = buildHomepageSummary(STATS, CHANNEL_SITES, SITES, NOW); assert.equal(s.version, HOMEPAGE_SUMMARY_VERSION); assert.deepEqual( @@ -144,3 +144,43 @@ test("the build month is never emitted, even in January (the year rolls back)", assert.deepEqual(s.monthly!.map((m) => m.month), ["2025-12"]); assert.equal(s.monthlyUnplaced, 1); }); + +// Release 8 review of slice H, the two LOWs (release 9). +test("a transcript bucketize drops (a future transcribedDate) still keeps placed + unplaced = official", () => { + const stats = [ + ...STATS, + // Transcribed "next year" (clock skew or a bad sidecar): the site card's + // bucketized total drops it, but it is placed by its upload month. + stat({ channelSlug: "a1", id: "x9", uploadDate: "20251201", transcribedDate: "20270101" }), + ]; + const s = buildHomepageSummary(stats, CHANNEL_SITES, SITES, NOW); + const placed = s.monthly!.reduce((a, m) => a + m.bySite.alpha + m.bySite.beta, 0); + assert.equal(s.official!.transcripts, 9); + assert.equal(placed + s.monthlyUnplaced!, s.official!.transcripts); + assert.deepEqual(s.monthly![1].bySite, { alpha: 1, beta: 0 }); +}); + +test("a site the activity filter drops gains no monthly key and no official count", () => { + const sites = [...SITES, site("gamma", ["g1"], "https://gamma.example")]; + const stats = [ + ...STATS, + // gamma's only transcript has a transcribedDate bucketize cannot place, + // so gamma has no activity and is filtered out of `sites`. + stat({ channelSlug: "g1", id: "w1", uploadDate: "20251101", transcribedDate: "20270101", downloadedDate: null }), + ]; + const s = buildHomepageSummary(stats, { ...CHANNEL_SITES, g1: ["gamma"] }, sites, NOW); + assert.ok(!s.sites.some((x) => x.siteId === "gamma")); + assert.ok(s.monthly!.every((m) => !("gamma" in m.bySite))); + const placed = s.monthly!.reduce((a, m) => a + m.bySite.alpha + m.bySite.beta, 0); + assert.equal(placed + s.monthlyUnplaced!, s.official!.transcripts); +}); + +test("gone counts only a deleted record we hold (it has a downloadedDate)", () => { + const stats = [ + ...STATS, + stat({ channelSlug: "b1", id: "y9", status: "deleted", downloadedDate: null, hasTranscript: false, transcribedDate: null }), + ]; + const s = buildHomepageSummary(stats, CHANNEL_SITES, SITES, NOW); + assert.equal(s.sites.find((x) => x.siteId === "beta")!.gone, 0); + assert.equal(s.official!.gone, 1); +}); diff --git a/common/lib/homepageSummary.ts b/common/lib/homepageSummary.ts @@ -347,12 +347,26 @@ export function buildHomepageSummary( let transcribedThisMonth = 0; let downloadedThisMonth = 0; const channelSet = new Set<string>(); - // Per public site: channels with a transcript, recordings, seconds, gone. - type SiteAcc = { channels: Set<string>; recordings: number; seconds: number; gone: number }; + // Per public site: channels with a transcript, recordings, transcripts, + // seconds, gone. `transcripts` is counted HERE, in the same pass that places + // or leaves unplaced each transcript, so `placed + unplaced = + // official.transcripts` holds even for a `transcribedDate` that bucketize + // drops (a future or malformed month). + type SiteAcc = { + channels: Set<string>; + recordings: number; + transcripts: number; + seconds: number; + gone: number; + }; const siteAcc = new Map<string, SiteAcc>(); const accOf = (id: string): SiteAcc => { let a = siteAcc.get(id); - if (!a) siteAcc.set(id, (a = { channels: new Set(), recordings: 0, seconds: 0, gone: 0 })); + if (!a) + siteAcc.set( + id, + (a = { channels: new Set(), recordings: 0, transcripts: 0, seconds: 0, gone: 0 }), + ); return a; }; // Upload-month placement of transcribed public-universe records. @@ -376,8 +390,10 @@ export function buildHomepageSummary( channelName.set(s.channelSlug, s.channel); const acc = accOf(siteId); if (s.downloadedDate) acc.recordings += 1; - if (s.status === "deleted") acc.gone += 1; + // "Gone at the source, still here": only a record we actually hold. + if (s.status === "deleted" && s.downloadedDate) acc.gone += 1; if (hasTx) { + acc.transcripts += 1; acc.channels.add(s.channelSlug); acc.seconds += s.duration > 0 ? s.duration : 0; const um = uploadMonthOf(s.uploadDate); @@ -483,7 +499,10 @@ export function buildHomepageSummary( for (const it of uploadItems) { const i = monthIndex.get(it.month); if (i === undefined) continue; // unreachable: every item's month is in range - monthly[i].bySite[it.siteId] = (monthly[i].bySite[it.siteId] ?? 0) + 1; + // A site the activity filter dropped has no key here and gets none: its + // transcripts are not placed, and not counted in `official` either. + if (!(it.siteId in monthly[i].bySite)) continue; + monthly[i].bySite[it.siteId] += 1; } // The family's own totals — hours summed in seconds, then rounded once. @@ -493,7 +512,10 @@ export function buildHomepageSummary( sites: summarySites.length, channels: summarySites.reduce((a, s) => a + (s.channels ?? 0), 0), recordings: summarySites.reduce((a, s) => a + (s.recordings ?? 0), 0), - transcripts: summarySites.reduce((a, s) => a + s.transcribed.total, 0), + transcripts: keptIds.reduce( + (a, id) => a + (siteAcc.get(id)?.transcripts ?? 0), + 0, + ), hoursArchived: Math.round(officialSeconds / 3600), gone: summarySites.reduce((a, s) => a + (s.gone ?? 0), 0), }; diff --git a/homepage/app/components/ArchiveGrowthChart.tsx b/homepage/app/components/ArchiveGrowthChart.tsx @@ -8,7 +8,8 @@ import { } from "yt-dlp-transcript-common/lib/homepageChart"; // The front page's showpiece: every official instance's back catalogue as -// stacked strata, one month per step, from the oldest upload to this month. +// stacked strata, one month per step, from the oldest upload to the last +// complete month (the build month is partial, so the summary never emits it). // // BUILD-TIME ONLY. A server component that renders inline SVG from the summary; // no chart library and no client JS ships on `/`. The hover layer is native: diff --git a/homepage/app/components/FamilyStats.tsx b/homepage/app/components/FamilyStats.tsx @@ -25,7 +25,14 @@ export function FamilyStats({ const builtOk = built && !Number.isNaN(built.getTime()); return ( <div className="flex flex-col gap-3"> - <dl className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-7 border-t border-l border-[var(--border)]"> + {/* Seven lg columns when the gone cell (span 2) is drawn, five when it + is not — otherwise the row ends in two empty columns. Both class + strings are complete literals (Tailwind v4 scans source). */} + <dl + className={`grid grid-cols-2 sm:grid-cols-3 ${ + official.gone > 0 ? "lg:grid-cols-7" : "lg:grid-cols-5" + } border-t border-l border-[var(--border)]`} + > {cells.map((c, i) => ( <div key={c.label} diff --git a/homepage/app/page.tsx b/homepage/app/page.tsx @@ -122,8 +122,8 @@ export default function Home() { )} {/* ── What it does ─────────────────────────────────────────────────── */} - {/* One paragraph, not three titled panels: without titles, three - one-sentence items are just a paragraph with bullets. */} + {/* Three short paragraphs, not three titled panels: without titles, + three one-sentence items read as prose, not as a feature grid. */} <section className="border-t border-[var(--border)]"> <div className={`${CONTAINER} py-14 sm:py-20`}> <h2 className="font-display text-2xl font-semibold text-[var(--foreground)]">