Archilyzer · Source

archilyzer

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

commit f0e532a02f5e303e574390b28c04a758f804fe64
parent 125ae785d7667c6146ee4c6d03ef30f93280f0ce
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 25 Sep 2026 17:00:34 -0400

common: the yt-dlp clip command shows with transcriptDownloads off

The operator (2026-09-25): "Oh no yt-dlp stays, I was only talking about the
zip downloads." The yt-dlp button copies a `yt-dlp --download-sections` line to
the clipboard and serves no file, so it is not a download: TranscriptModal now
renders it (both states) whatever `transcriptDownloads` says. The Download menu
and Copy MD stay behind the switch. Accessible names unchanged.

The siteSchema doc (SITE.md regenerated), the PlayerFeatures and homepage.ts
comments and the editor's site/hub form labels stop claiming the switch hides
the command; the site and hub transcript-downloads specs assert it present in
both states.

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

Diffstat:
MSITE.md | 2+-
Mcommon/components/PlayerProvider.tsx | 5+++--
Mcommon/components/TranscriptModal.tsx | 48++++++++++++++++++++++++------------------------
Mcommon/lib/homepage.ts | 4++--
Mcommon/lib/siteSchema.ts | 2+-
Meditor/app/sites/components/HomepageConfigForm.tsx | 7+++----
Meditor/app/sites/components/SiteForm.tsx | 8++++----
Mexport/e2e-hub/transcript-downloads.spec.ts | 13++++++++++---
Mexport/e2e/transcript-downloads.spec.ts | 19+++++++++++++------
9 files changed, 61 insertions(+), 47 deletions(-)

diff --git a/SITE.md b/SITE.md @@ -188,7 +188,7 @@ 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. +Whether a visitor gets the per-video export controls in the transcript modal: the Download menu (txt / srt / json) and Copy MD. Opt-OUT: absent/true = on, only an explicit `false` hides them. The yt-dlp clip command is not a download (it copies a line, serves no file) and shows either way. The machine contract (`/corpus.json`, manifests, shards, `llms.txt`) is served either way. The editor always shows them. Default: `true` diff --git a/common/components/PlayerProvider.tsx b/common/components/PlayerProvider.tsx @@ -113,8 +113,9 @@ type ClipState = { // them from its site.json (`transcriptDownloads`); the editor mounts nothing // and every host that passes nothing keeps everything on. export type PlayerFeatures = { - // The three per-video export controls: Copy download command, the Download - // menu (txt / srt / json) and Copy MD. + // The two per-video export controls: the Download menu (txt / srt / json) + // and Copy MD. The yt-dlp clip command is not one of them — it copies a + // line, serves no file, and shows either way. transcriptDownloads: boolean; }; diff --git a/common/components/TranscriptModal.tsx b/common/components/TranscriptModal.tsx @@ -74,8 +74,10 @@ export default function TranscriptModal() { seekTo, features, } = usePlayer(); - // Off on a site whose site.json sets `transcriptDownloads: false`: the three - // per-video export controls are not rendered at all (Share stays). + // Off on a site whose site.json sets `transcriptDownloads: false`: the two + // per-video export controls (the Download menu and Copy MD) are not rendered + // at all. The yt-dlp command stays: it copies a line to the clipboard and + // serves no file, so it is not a download. Share stays too. const showDownloads = features.transcriptDownloads; const { currentTime } = usePlayerTime(); const scrollRef = useRef<HTMLDivElement | null>(null); @@ -404,28 +406,26 @@ export default function TranscriptModal() { clipEnd={clipEnd} onClear={clearClip} /> - {showDownloads && ( - <ControlButton - title={ - canDownload - ? copied - ? "Copied!" - : "Copy yt-dlp download command for this clip" - : "Mark both clip start and end first" - } - onClick={onCopy} - icon={ - copied ? ( - <CheckIcon className={iconSize} /> - ) : ( - <TerminalIcon className={iconSize} /> - ) - } - label="yt-dlp" - char={copied ? "\u2713" : "\u2193"} - disabled={!canDownload} - /> - )} + <ControlButton + title={ + canDownload + ? copied + ? "Copied!" + : "Copy yt-dlp download command for this clip" + : "Mark both clip start and end first" + } + onClick={onCopy} + icon={ + copied ? ( + <CheckIcon className={iconSize} /> + ) : ( + <TerminalIcon className={iconSize} /> + ) + } + label="yt-dlp" + char={copied ? "\u2713" : "\u2193"} + disabled={!canDownload} + /> <Divider /> diff --git a/common/lib/homepage.ts b/common/lib/homepage.ts @@ -33,8 +33,8 @@ export type HomepageConfig = { // Cloudflare Pages project the hub deploys to (e.g. "archilyzer-hub"; never // "archilyzer", the homepage's — deployHub refuses it). cloudflareProject?: string; - // The transcript modal's per-video export controls (Download menu, Copy MD, - // Copy download command) on the hub's Browse and Ask pages — the same switch + // The transcript modal's per-video export controls (Download menu, Copy MD; + // the yt-dlp clip command shows either way) on the hub's Browse and Ask pages — the same switch // as site.json `transcriptDownloads`. Opt-OUT: absent = on; only an explicit // false is read, and only false is written. transcriptDownloads?: boolean; diff --git a/common/lib/siteSchema.ts b/common/lib/siteSchema.ts @@ -120,7 +120,7 @@ export const SITE_FIELD_DOCS: FieldDocs<Site> = { 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.", + "Whether a visitor gets the per-video export controls in the transcript modal: the Download menu (txt / srt / json) and Copy MD. Opt-OUT: absent/true = on, only an explicit `false` hides them. The yt-dlp clip command is not a download (it copies a line, serves no file) and shows either way. 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: diff --git a/editor/app/sites/components/HomepageConfigForm.tsx b/editor/app/sites/components/HomepageConfigForm.tsx @@ -69,13 +69,12 @@ export function HomepageConfigForm({ config }: { config: HomepageConfig }) { defaultChecked={config.transcriptDownloads !== false} className="accent-brand" /> - Per-video transcript downloads (Download menu, Copy Markdown, Copy download - command) + Per-video transcript downloads (Download menu, Copy Markdown) </label> <p className="-mt-2 text-xs text-muted-foreground"> On by default: the hub&apos;s transcript viewer offers the transcript as a - file, as Markdown, and a yt-dlp command for a marked clip, for every - archive it federates. Turn off to hide all three on the hub. + file and as Markdown, for every archive it federates. Turn off to hide + both on the hub; the yt-dlp command for a marked clip stays. </p> <div className="flex items-center gap-3"> <button diff --git a/editor/app/sites/components/SiteForm.tsx b/editor/app/sites/components/SiteForm.tsx @@ -297,13 +297,13 @@ export function SiteForm({ initial, channels, allSites, isNew }: Props) { defaultChecked={initial.transcriptDownloads !== false} className="accent-brand" /> - Per-video transcript downloads (Download menu, Copy Markdown, Copy download - command) + Per-video transcript downloads (Download menu, Copy Markdown) </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 + txt/srt/json file and as Markdown. Turn off to hide both on this site; + the yt-dlp command for a marked clip is not a download and stays. The + machine contract (corpus.json, manifests, shards) is published either way. </p> <label className="flex items-center gap-2 text-sm"> diff --git a/export/e2e-hub/transcript-downloads.spec.ts b/export/e2e-hub/transcript-downloads.spec.ts @@ -26,7 +26,6 @@ test.afterEach(() => { function exportControls(page: Page) { return [ - page.getByRole("button", { name: "Mark both clip start and end first" }), page.getByRole("button", { name: "Download this transcript as a file" }), page.getByRole("button", { name: "Copy this transcript as Markdown (for AI)", @@ -34,6 +33,12 @@ function exportControls(page: Page) { ]; } +// The yt-dlp clip command copies a line and serves no file, so it is not a +// download: it shows whatever the switch says (unmarked state). +function ytDlpButton(page: Page) { + return page.getByRole("button", { name: "Mark both clip start and end first" }); +} + // A bare channel/video id is same-origin content, which the hub reads from its // own origin — installRoutes serves it. async function openModal(page: Page) { @@ -46,16 +51,17 @@ async function openModal(page: Page) { await expect(page.locator("li", { hasText: "beta line" })).toBeVisible(); } -test("hub, no homepage.json key: the three export controls are present", async ({ +test("hub, no homepage.json key: the export controls and yt-dlp are present", async ({ page, }) => { await openModal(page); for (const control of exportControls(page)) { await expect(control).toBeVisible(); } + await expect(ytDlpButton(page)).toBeVisible(); }); -test("hub, homepage.json transcriptDownloads: false hides all three; Share stays", async ({ +test("hub, homepage.json transcriptDownloads: false hides both; yt-dlp and Share stay", async ({ page, }) => { fs.mkdirSync(path.dirname(HOMEPAGE_FILE), { recursive: true }); @@ -64,6 +70,7 @@ test("hub, homepage.json transcriptDownloads: false hides all three; Share stays for (const control of exportControls(page)) { await expect(control).toHaveCount(0); } + await expect(ytDlpButton(page)).toBeVisible(); await expect( page.getByRole("button", { name: "Copy share link at current time" }), ).toBeVisible(); diff --git a/export/e2e/transcript-downloads.spec.ts b/export/e2e/transcript-downloads.spec.ts @@ -4,8 +4,8 @@ import { expect, test, type Page } from "@playwright/test"; import { CHANNEL_SLUG, VIDEO_TRANSCRIPT_ONLY } from "./fixtures/data"; import { expectModalOpen, installRoutes } from "./helpers"; -// site.json `transcriptDownloads` gates the transcript modal's three per-video -// export controls. The suite serves ONE site (SITE_ID=testsite, SITES_DIR = +// site.json `transcriptDownloads` gates the transcript modal's two per-video +// export controls (Download menu, Copy MD); the yt-dlp clip command is not one. The suite serves ONE site (SITE_ID=testsite, SITES_DIR = // e2e/fixtures/sites) and currentSite() re-reads site.json on every dev render, // so the off case flips the key in the fixture for the duration of one test and // puts the file back. A run killed mid-test leaves the key behind; @@ -35,11 +35,10 @@ test.afterEach(() => { fs.writeFileSync(SITE_FILE, pristine); }); -// The three controls, by the accessible names they carry once the transcript +// The two controls, by the accessible names they carry once the transcript // has loaded and no clip is marked. function exportControls(page: Page) { return [ - page.getByRole("button", { name: "Mark both clip start and end first" }), page.getByRole("button", { name: "Download this transcript as a file" }), page.getByRole("button", { name: "Copy this transcript as Markdown (for AI)", @@ -47,6 +46,12 @@ function exportControls(page: Page) { ]; } +// The yt-dlp clip command copies a line and serves no file, so it is not a +// download: it shows whatever the switch says (unmarked state). +function ytDlpButton(page: Page) { + return page.getByRole("button", { name: "Mark both clip start and end first" }); +} + async function openModal(page: Page) { await installRoutes(page); await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}`); @@ -55,19 +60,20 @@ async function openModal(page: Page) { await expect(page.locator("li", { hasText: "beta line" })).toBeVisible(); } -test("default (key absent): the three export controls are present", async ({ +test("default (key absent): the export controls and yt-dlp are present", async ({ page, }) => { await openModal(page); for (const control of exportControls(page)) { await expect(control).toBeVisible(); } + await expect(ytDlpButton(page)).toBeVisible(); await expect( page.getByRole("button", { name: "Copy share link at current time" }), ).toBeVisible(); }); -test("transcriptDownloads: false hides all three; Share stays", async ({ +test("transcriptDownloads: false hides both; yt-dlp and Share stay", async ({ page, }) => { writeSiteFile({ ...JSON.parse(pristine), transcriptDownloads: false }); @@ -75,6 +81,7 @@ test("transcriptDownloads: false hides all three; Share stays", async ({ for (const control of exportControls(page)) { await expect(control).toHaveCount(0); } + await expect(ytDlpButton(page)).toBeVisible(); // Nothing else in the bar went with them. await expect( page.getByRole("button", { name: "Copy share link at current time" }),