commit 41f43b3ba1471294a0dde8ef3a0295d279b1ce12
parent 9a61334a986324dae398051625c8f92aed5d8122
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 24 Sep 2026 21:56:14 -0400
common: site.json transcriptDownloads — per-video export controls, opt-out, absent = on
The archives pattern: type, SITE_FIELD_DOCS entry, zod field that is off only
on an explicit false, and siteToDisk writes the key only when false. SITE.md
regenerated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/SITE.md b/SITE.md
@@ -26,6 +26,7 @@ Regenerate this file with `pnpm --filter yt-dlp-transcript-common exec tsx bin/f
| [`pwa`](#pwa) | `false` |
| [`archives`](#archives) | `true` |
| [`duplicates`](#duplicates) | `true` |
+| [`transcriptDownloads`](#transcriptdownloads) | `true` |
| [`archiveMaxBytes`](#archivemaxbytes) | absent |
| [`hubUrl`](#huburl) | absent |
@@ -185,6 +186,12 @@ Whether this site publishes the Duplicates page (and its header link). Opt-OUT:
Default: `true`
+## `transcriptDownloads`
+
+Whether a visitor gets the per-video export controls in the transcript modal: the Download menu (txt / srt / json), Copy MD and Copy download command. Opt-OUT: absent/true = on, only an explicit `false` hides them. The machine contract (`/corpus.json`, manifests, shards, `llms.txt`) is served either way. The editor always shows them.
+
+Default: `true`
+
## `archiveMaxBytes`
Per-site served-file size cap in bytes: any archive larger is dropped from what is served and flagged in the manifest, so a capped host (Cloudflare Pages: 25 MB) will not reject the deploy. 0 = no cap. Absent = the global default. Negative or non-numeric values are dropped.
diff --git a/common/lib/siteSchema.test.ts b/common/lib/siteSchema.test.ts
@@ -58,6 +58,7 @@ test("empty, null, [] and a number all read as the defaults, every key emitted",
assert.equal(want.defaultGroupId, "default");
assert.equal(want.archives, true);
assert.equal(want.duplicates, true);
+ assert.equal(want.transcriptDownloads, true);
assert.equal(want.pwa, false);
assert.deepEqual(want.relatedSites, []);
assert.equal(want.socialLinks, undefined);
@@ -104,13 +105,19 @@ test("socialLinks: absent inherits (undefined), [] overrides", () => {
assert.equal(parseSite("s", { socialLinks: "x" }).socialLinks, undefined);
});
-test("archives / duplicates are off only when explicitly false", () => {
+test("archives / duplicates / transcriptDownloads are off only when explicitly false", () => {
for (const v of [undefined, true, 0, "false", null]) {
assert.equal(parseSite("s", { archives: v }).archives, true, String(v));
assert.equal(parseSite("s", { duplicates: v }).duplicates, true, String(v));
+ assert.equal(
+ parseSite("s", { transcriptDownloads: v }).transcriptDownloads,
+ true,
+ String(v),
+ );
}
assert.equal(parseSite("s", { archives: false }).archives, false);
assert.equal(parseSite("s", { duplicates: false }).duplicates, false);
+ assert.equal(parseSite("s", { transcriptDownloads: false }).transcriptDownloads, false);
});
test("siteToDisk persists only the non-defaults", () => {
@@ -129,6 +136,7 @@ test("siteToDisk persists only the non-defaults", () => {
parseSite("s", {
archives: false,
duplicates: false,
+ transcriptDownloads: false,
pwa: true,
archiveMaxBytes: 1024.9,
siteUrl: "https://a.example/",
@@ -141,6 +149,7 @@ test("siteToDisk persists only the non-defaults", () => {
);
assert.equal(full.archives, false);
assert.equal(full.duplicates, false);
+ assert.equal(full.transcriptDownloads, false);
assert.equal(full.pwa, true);
assert.equal(full.archiveMaxBytes, 1024);
assert.equal(full.siteUrl, "https://a.example");
@@ -177,6 +186,7 @@ function fixtures(): Array<[string, unknown]> {
archives: false,
archiveMaxBytes: -1,
duplicates: false,
+ transcriptDownloads: false,
hubUrl: "ftp://nope",
bogus: true,
},
@@ -225,5 +235,6 @@ test("writeSite → getSite round-trips, and the file holds only non-defaults",
const disk = JSON.parse(await readFile(siteConfigFile(paths, "s"), "utf8"));
assert.equal(disk.archives, false);
assert.equal("duplicates" in disk, false);
+ assert.equal("transcriptDownloads" in disk, false);
assert.equal("pwa" in disk, false);
});
diff --git a/common/lib/siteSchema.ts b/common/lib/siteSchema.ts
@@ -85,6 +85,7 @@ export type Site = {
pwa?: boolean;
archives?: boolean;
duplicates?: boolean;
+ transcriptDownloads?: boolean;
archiveMaxBytes?: number;
hubUrl?: string;
};
@@ -118,6 +119,8 @@ export const SITE_FIELD_DOCS: FieldDocs<Site> = {
"Whether the site build generates downloadable transcript/live-chat archive zips (and links them on the Downloads page). Opt-OUT: absent/true = on, only an explicit `false` disables. Also gated by the global setting and a per-build flag.",
duplicates:
"Whether this site publishes the Duplicates page (and its header link). Opt-OUT: absent/true = on, only an explicit `false` hides it. Even when on, the page auto-hides when the site has no in-scope duplicate clusters.",
+ transcriptDownloads:
+ "Whether a visitor gets the per-video export controls in the transcript modal: the Download menu (txt / srt / json), Copy MD and Copy download command. Opt-OUT: absent/true = on, only an explicit `false` hides them. The machine contract (`/corpus.json`, manifests, shards, `llms.txt`) is served either way. The editor always shows them.",
archiveMaxBytes:
"Per-site served-file size cap in bytes: any archive larger is dropped from what is served and flagged in the manifest, so a capped host (Cloudflare Pages: 25 MB) will not reject the deploy. 0 = no cap. Absent = the global default. Negative or non-numeric values are dropped.",
hubUrl:
@@ -239,6 +242,9 @@ export const siteFieldsSchema = z.object({
// Opt-out: only an explicit false disables. Absent/true stays on.
archives: settingsField((v): boolean => v !== false).describe(d.archives),
duplicates: settingsField((v): boolean => v !== false).describe(d.duplicates),
+ transcriptDownloads: settingsField((v): boolean => v !== false).describe(
+ d.transcriptDownloads,
+ ),
archiveMaxBytes: settingsField(archiveMaxBytesOf).describe(d.archiveMaxBytes),
hubUrl: settingsField(parseSiteUrl).describe(d.hubUrl),
});
@@ -319,6 +325,7 @@ export function siteToDisk(site: Site): Site {
// Persist only the non-default: archives is on unless explicitly disabled.
...(site.archives === false ? { archives: false } : {}),
...(site.duplicates === false ? { duplicates: false } : {}),
+ ...(site.transcriptDownloads === false ? { transcriptDownloads: false } : {}),
...(archiveMaxBytes !== undefined ? { archiveMaxBytes } : {}),
...(hubUrl ? { hubUrl } : {}),
};