Archilyzer · Source

archilyzer

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

commit 9f6beed2c115142e5b3036411c5133f4ebf877fd
parent 8119cfacbdbd3870d80a509df9e5ed9066222639
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 24 Sep 2026 21:56:14 -0400

sites: form checkbox for per-video transcript downloads

Beside archives, same opt-out idiom: an unticked box saves
transcriptDownloads: false, a ticked one drops the key. sites-crud gains a
round-trip for both opt-outs (save, file, reload, tick back, key gone).

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

Diffstat:
Meditor/app/sites/actions.ts | 3+++
Meditor/app/sites/components/SiteForm.tsx | 16++++++++++++++++
Meditor/e2e/sites-crud.spec.ts | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 74 insertions(+), 0 deletions(-)

diff --git a/editor/app/sites/actions.ts b/editor/app/sites/actions.ts @@ -76,6 +76,8 @@ export async function saveSiteAction( const archives = formData.get("archives") === "on"; // Duplicates default on, same opt-out idiom as archives. const duplicates = formData.get("duplicates") === "on"; + // Per-video transcript downloads default on, same opt-out idiom. + const transcriptDownloads = formData.get("transcriptDownloads") === "on"; const archiveMaxMBRaw = String(formData.get("archiveMaxMB") ?? "").trim(); let archiveMaxBytes: number | undefined; if (archiveMaxMBRaw) { @@ -181,6 +183,7 @@ export async function saveSiteAction( ...(pwa ? { pwa: true } : {}), ...(archives ? {} : { archives: false }), ...(duplicates ? {} : { duplicates: false }), + ...(transcriptDownloads ? {} : { transcriptDownloads: false }), ...(archiveMaxBytes !== undefined ? { archiveMaxBytes } : {}), ...(relatedSites.length > 0 ? { relatedSites } : {}), }; diff --git a/editor/app/sites/components/SiteForm.tsx b/editor/app/sites/components/SiteForm.tsx @@ -293,6 +293,22 @@ export function SiteForm({ initial, channels, allSites, isNew }: Props) { <label className="flex items-center gap-2 text-sm"> <input type="checkbox" + name="transcriptDownloads" + defaultChecked={initial.transcriptDownloads !== false} + className="accent-brand" + /> + Per-video transcript downloads (Download menu, Copy Markdown, Copy download + command) + </label> + <p className="-mt-2 text-xs text-muted-foreground"> + On by default: a visitor&apos;s transcript viewer offers the transcript as a + txt/srt/json file, as Markdown, and a yt-dlp command for a marked clip. + Turn off to hide all three on this site. The machine contract + (corpus.json, manifests, shards) is published either way. + </p> + <label className="flex items-center gap-2 text-sm"> + <input + type="checkbox" name="duplicates" defaultChecked={initial.duplicates !== false} className="accent-brand" diff --git a/editor/e2e/sites-crud.spec.ts b/editor/e2e/sites-crud.spec.ts @@ -290,6 +290,61 @@ test("inline-chips flag round-trips; seeded default group has it on", async ({ await expect(chipToggles.nth(1)).not.toBeChecked(); }); +test("archives + per-video transcript downloads opt-outs round-trip", async ({ + page, +}) => { + await resetData("empty"); + await writeSite("optout", { siteTitle: "Opt Out" }); + + type OptOutSiteFile = { archives?: boolean; transcriptDownloads?: boolean }; + const file = "test-transcripts/sites/optout/site.json"; + const archives = page.getByRole("checkbox", { + name: /generate downloadable archive zips/i, + }); + const downloads = page.getByRole("checkbox", { + name: /per-video transcript downloads/i, + }); + const save = async () => { + await page.getByRole("button", { name: /save site/i }).click(); + await expect( + page.getByRole("status").filter({ hasText: "Saved" }), + ).toBeVisible(); + }; + + // Both default ON: absent from the file, ticked in the form. + await page.goto("/sites/optout"); + await expect(archives).toBeChecked(); + await expect(downloads).toBeChecked(); + + await archives.uncheck(); + await downloads.uncheck(); + await save(); + // Poll: the "Saved" status and the on-disk write can land slightly apart. + await expect(async () => { + const site = await readJson<OptOutSiteFile>(file); + expect(site.archives).toBe(false); + expect(site.transcriptDownloads).toBe(false); + }).toPass({ timeout: 10_000 }); + + await page.goto("/sites/optout"); + await expect(archives).not.toBeChecked(); + await expect(downloads).not.toBeChecked(); + + // Ticking back on removes the key — only the non-default is persisted. + await archives.check(); + await downloads.check(); + await save(); + await expect(async () => { + const site = await readJson<OptOutSiteFile>(file); + expect("archives" in site).toBe(false); + expect("transcriptDownloads" in site).toBe(false); + }).toPass({ timeout: 10_000 }); + + await page.goto("/sites/optout"); + await expect(archives).toBeChecked(); + await expect(downloads).toBeChecked(); +}); + test("first-run migrate button appears only when no sites exist", async ({ page, }) => {