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:
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)]">