Archilyzer · Source

archilyzer

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

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

Merge one-core/r8-ytdlp-button — release 8 slice Z: the yt-dlp clip command shows with transcriptDownloads off (only the file/Markdown downloads are gated)

Co-Authored-By: Claude Fable 5.1 <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/CHANGELOG.md | 1+
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+++++++++++++------
Mplans/release-8.md | 39+++++++++++++++++++++++++++++++++++++++
11 files changed, 101 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/CHANGELOG.md b/editor/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] +- **The yt-dlp clip command is back on sites with transcript downloads turned off.** Turning off `transcriptDownloads` (site.json, or the hub's homepage.json) hid three buttons in the transcript viewer. One of them, the yt-dlp button, only copies a `yt-dlp --download-sections` command for a marked clip to the clipboard and serves no file, so it is not a download. It now shows on every site. The switch still hides the Download menu (txt / srt / json) and Copy MD. The site and hub form labels in the editor say so. No setting changed; a site picks this up at its next build and deploy. - **The project homepage is rewritten, and it can be deployed as a preview.** The front page of `archilyzer.pages.dev` now opens on one line and a chart. The chart shows the official instances' transcripts by the month each video was published, from 2009 to now, stacked by instance. It is drawn when the site is built, so no chart script loads. Under it is one strip of numbers for the official instances: hours of speech, transcripts, recordings, channels, instances, and recordings gone at the source but still here. The **Official instances** section has a card for each public site, with its channels, recordings, transcripts and hours. **What it does** is now three short paragraphs. The recent-acquisitions list, "How it works" and "What this isn't" are gone from the page. The homepage summary (`homepage/public/homepage-summary.json`) is version 5. It adds `monthly`, `official` and per-site numbers and removes nothing, so `/stats` is unchanged. `archilyzer deploy homepage --preview <branch>` deploys a preview the way `deploy hub --preview` does. Without the flag it still deploys to production (`main`). - **A channel's video list shows titles, and you can search by them.** On `/channels/<slug>/videos`, each row now shows the video's title, with its id in smaller type underneath. Search matches the title or the id, ignoring case. The title comes from the transcript index for transcribed videos, from the channel's metadata scan for videos that were listed but never downloaded, and otherwise from the video's `metadata.info.json`. A video none of these name shows its id, as before. Each row's selection checkbox and link are named by the id as before, and the order is unchanged (by id). - **An undownloaded video's page shows its title and details.** The video page used to show a bare id for any video without a `metadata.info.json`. If the channel's metadata scan has read the video, the page now shows its title, upload date and duration from the scan, marked *from the listing scan — not downloaded*. Any video page with a description, from either source, has a **Description** section, collapsed by default. 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" }), diff --git a/plans/release-8.md b/plans/release-8.md @@ -306,6 +306,45 @@ runs there. - `KpiHeader.tsx` and `RecentAdditions.tsx` are unchanged. `/stats` still uses `KpiHeader`. The rail is unused on `/` but kept, per the brief. +### Slice Z, as shipped — the yt-dlp command button is not a download (2026-09-25) + +Branch `one-core/r8-ytdlp-button` off `main` `90a6aae6`. **The operator (2026-09-25):** "Oh no yt-dlp +stays, I was only talking about the zip downloads. Re-add ytdlp button asap and redeploy to all sites +that removed it." Release 5 slice X put three controls behind `transcriptDownloads: false`; the yt-dlp +button was one of them, though it only copies a `yt-dlp --download-sections … "<url>"` line to the +clipboard (`PlayerProvider.copyDownloadCommand`) and serves no file. All five live sites carry the flag, +so every public modal lost it. + +**What changed.** `TranscriptModal.tsx` renders the yt-dlp button, in both its states ("Mark both clip +start and end first" / "Copy yt-dlp download command for this clip"), whatever the switch says; the +Download menu and Copy MD stay behind it. Accessible names unchanged. The `siteSchema` doc for +`transcriptDownloads` (SITE.md regenerated through `bin/file-schemas-docs.ts`, `--check` clean), the +`PlayerFeatures` and `homepage.ts` comments, and the editor's site and hub form labels +(`SiteForm.tsx`, `HomepageConfigForm.tsx` — "Per-video transcript downloads (Download menu, Copy +Markdown)", hint says the yt-dlp command stays) stop claiming the switch hides it. The last four files +were outside the brief's ownership list; they are comment/label text only and no sibling r8 branch +touches them. No key, default or data format changed. The editor never mounts these controls, so no +editor spec changed; no editor spec reads the form label. + +| Commit | What | +|---|---| +| `4ca398a9` | common: the yt-dlp clip command shows with transcriptDownloads off (modal, schema doc + SITE.md, comments, form labels, both transcript-downloads specs) | +| this commit | plans: the record; `[Unreleased]` bullet | + +**Specs.** `export/e2e/transcript-downloads.spec.ts` and `export/e2e-hub/transcript-downloads.spec.ts` +now name two export controls and assert the yt-dlp button ("Mark both clip start and end first") +visible in both the default and the off case. `player-control-bar.spec.ts` copies the command on the +default (on) site and needed no change; `archives-off` and `modal-*` never name these controls. + +**Gates.** tsc (`pnpm -r … tsc --noEmit`) clean. `pnpm --filter export exec next build` OK. Export e2e +(`archives-off modal-open modal-share modal-close modal-digest transcript-downloads +player-control-bar`): 22 passed, 0 failed, 1.2 min. `e2e:hub`: 8 passed, 0 failed, 23.8 s (count +unchanged). Common/editor unit, test:scripts, mcp and the editor build were not run: the code change is +one JSX condition plus text (the brief's gate list). **Numbers: none.** + +**Pending, the parent's:** merge, then rebuild and redeploy every site that has `transcriptDownloads: +false` (all five) and the hub, from the primary. + ## Rollout 2026-09-25 (late afternoon) — `0e72ef73` live on :3001 (second restart of the day) The second editor restart of the day. Release 7 (`bb3dbb4c`) had gone live at 13:11. Two things