Archilyzer · Source

archilyzer

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

commit fc5ccd226bdbdb00845daa9c286a17ee4f6d06c4
parent d1f9970bc047b12c02b8f60173a080131aa17ba5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 09:35:18 -0400

editor, common: the build-mode stub is dropped (O6c, the operator's ruling)

"If docker build mode isn't ready yet, just drop it for now rather than
keep the stub." `buildPipeline.mode` was a label nothing read: Build all /
Build & deploy all use containers whenever `docker version` answers and
build serially on the host otherwise, and nothing about that changes.

- Gone: the `mode` key and `BuildMode` / `isBuildMode`
  (common/lib/settingsSchema.ts; SETTINGS.md and settings.json.example
  regenerated); the /sites Basic | Docker toggle (BuildModeToggle.tsx,
  buildModeAction.ts, its use on /sites); the Settings form's select and
  actions.ts' handling of it; the publish tab's "Build mode … is set on the
  Sites page" line. Copy that named the mode, in the Build all paragraph,
  the per-site panel and three comments, says only what the code does.
- Stays: the container path and `maxParallelBuilds`, `dockerImage`,
  `dockerfile` (the Settings field loses its "(Docker mode)" suffix).
- An existing settings.json with `buildPipeline.mode` (the operator's says
  "basic") loads without error and loses the key on the next save: the
  sanitizer builds its output from the three real fields only. New tests:
  settingsSchema.test.ts (the sanitizer and the whole-file parse drop
  `mode` for any value) and settingsWrite.test.ts (a file carrying it
  loads, and a save writes the three fields and no `mode`).
- deploy-page.spec's toggle test becomes "/sites has no build-mode toggle";
  settings.spec saves the three real fields and asserts no select and no
  `mode` on disk.

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

Diffstat:
MDockerfile.build | 2+-
MSETTINGS.md | 6++----
Mcommon/lib/settingsSchema.test.ts | 25+++++++++++++++++++++++++
Mcommon/lib/settingsSchema.ts | 32++++++++++++--------------------
Mcommon/lib/settingsWrite.test.ts | 29+++++++++++++++++++++++++++++
Mcommon/publish/build.ts | 2+-
Meditor/app/settings/actions.ts | 6+-----
Meditor/app/settings/components/SettingsForm.tsx | 22+++-------------------
Meditor/app/sites/[siteId]/publish/page.tsx | 8--------
Meditor/app/sites/components/BuildAllSitesButton.tsx | 8+++-----
Deditor/app/sites/components/BuildModeToggle.tsx | 66------------------------------------------------------------------
Meditor/app/sites/components/BuildSitesPanel.tsx | 10+++++-----
Meditor/app/sites/lib/buildAction.ts | 4++--
Deditor/app/sites/lib/buildModeAction.ts | 24------------------------
Meditor/app/sites/page.tsx | 10+++-------
Meditor/e2e/deploy-page.spec.ts | 57++++++++-------------------------------------------------
Meditor/e2e/settings.spec.ts | 22++++++----------------
Msettings.json.example | 1-
18 files changed, 101 insertions(+), 233 deletions(-)

diff --git a/Dockerfile.build b/Dockerfile.build @@ -1,4 +1,4 @@ -# Build image for the docker export pipeline (buildPipeline.mode = "docker"). +# Build image for the docker export pipeline (Build all, whenever a container engine answers). # # Bakes the repo source + installed deps so each per-site build container is # hermetic and reproducible. The corpus, the shared LMDB index, the .export-index diff --git a/SETTINGS.md b/SETTINGS.md @@ -556,14 +556,13 @@ Default: ## `buildPipeline` -The build pipeline's settings. A single site builds in the shared export/ tree, serialized on one queue. Build all sites (and Build & deploy all) builds every site at once, each in its own container (Dockerfile.build, the image and maxParallelBuilds below), then deploys them serially, whenever a container engine answers, and serially on the host when none does — see PUBLISH.md. `mode` is persisted and shown on /sites, but no build path reads it today: it is a label, not a switch. +The build pipeline's settings. A single site builds in the shared export/ tree, serialized on one queue. Build all sites (and Build & deploy all) builds every site at once, each in its own container (Dockerfile.build, the image and maxParallelBuilds below), then deploys them serially, whenever a container engine answers, and serially on the host when none does — see PUBLISH.md. There is no mode switch; a `mode` key left in an older file is dropped on the next save. #### `buildPipeline` | Key | Default | Description | |---|---|---| -| `mode` | `"basic"` | "basic" or "docker". Persisted and shown on /sites, but no build path reads it today — a label, not a switch: Build all sites uses containers whenever a container engine answers, and builds serially on the host when none does, in either mode. | -| `maxParallelBuilds` | `2` | Cap on concurrent per-site container builds when Build all sites runs in containers (whenever a container engine answers, whatever `mode` says). Clamped to [1, BUILD_MAX_PARALLEL_MAX]. | +| `maxParallelBuilds` | `2` | Cap on concurrent per-site container builds when Build all sites runs in containers (whenever a container engine answers). Clamped to [1, BUILD_MAX_PARALLEL_MAX]. | | `dockerImage` | `"yt-dlp-transcript-browser-build"` | Tag of the reusable build image (built once, reused for every site). | | `dockerfile` | `"Dockerfile.build"` | Dockerfile path relative to the monorepo root, used to (re)build the image. | @@ -571,7 +570,6 @@ Default: ```json { - "mode": "basic", "maxParallelBuilds": 2, "dockerImage": "yt-dlp-transcript-browser-build", "dockerfile": "Dockerfile.build" diff --git a/common/lib/settingsSchema.test.ts b/common/lib/settingsSchema.test.ts @@ -171,6 +171,31 @@ function checkField( } // min / max / NaN / string / null / absent, for each clamped top-level field. +// THE RETIRED `buildPipeline.mode` (release 11, follow-up O6c). It was a label +// nothing read. A file that still has it — the operator's says "basic" — must +// load, keep the three real fields, and carry no `mode` onward. +test("a buildPipeline that still carries the retired mode loads, and the mode is gone", () => { + for (const mode of ["basic", "docker", "nonsense", 3]) { + const clean = S.sanitizeBuildPipeline({ + mode, + maxParallelBuilds: 3, + dockerImage: " my-image ", + dockerfile: "Dockerfile.build", + }); + assert.deepEqual(clean, { + maxParallelBuilds: 3, + dockerImage: "my-image", + dockerfile: "Dockerfile.build", + }); + assert.equal("mode" in clean, false, String(mode)); + } + // Through the whole-file parse, as every process reads it. + const parsed = siteSettingsSchema.parse({ buildPipeline: { mode: "basic", maxParallelBuilds: 5 } }); + assert.equal("mode" in parsed.buildPipeline, false); + assert.equal(parsed.buildPipeline.maxParallelBuilds, 5); + assert.equal("mode" in defaults().buildPipeline, false); +}); + test("maxTranscriptPageBytes clamps into [256 KiB, 20 MiB]", () => { checkField("maxTranscriptPageBytes", [ [0, S.TRANSCRIPT_PAGE_MIN_BYTES], diff --git a/common/lib/settingsSchema.ts b/common/lib/settingsSchema.ts @@ -414,28 +414,25 @@ export const DIGEST_SETTINGS_FIELD_DOCS: FieldDocs<DigestSettings> = { "fresh. Empty = default.", }; -// "basic" | "docker" — persisted and shown on /sites, but A LABEL TODAY: no -// build path reads it. Build all / Build & deploy all (editor buildAction.ts) -// fan out in containers (publish/build.ts, runDockerBuildAllPhase, capped by -// maxParallelBuilds) whenever `docker version` answers, and build serially on -// the host otherwise, whichever mode is set. Whether they should honour it is -// an open question for the operator (plans/release-11.md, slice O6). -export type BuildMode = "basic" | "docker"; - +// Build all / Build & deploy all (editor buildAction.ts, `archilyzer build all`) +// fan out in containers (publish/build.ts, runDockerBuildAllPhase) whenever +// `docker version` answers, and build serially on the host otherwise. There is +// no mode switch: the `mode` key ("basic" | "docker") was a label nothing read, +// and it was dropped on 2026-09-28 (release 11, follow-up O6c). A settings.json +// that still carries it loads, and loses it on the next save — the sanitizer +// below builds its output from the three real fields only. +// // Each field is documented in BUILD_PIPELINE_SETTINGS_FIELD_DOCS below (rendered into SETTINGS.md). export type BuildPipelineSettings = { - mode: BuildMode; maxParallelBuilds: number; dockerImage: string; dockerfile: string; }; export const BUILD_PIPELINE_SETTINGS_FIELD_DOCS: FieldDocs<BuildPipelineSettings> = { - mode: - "\"basic\" or \"docker\". Persisted and shown on /sites, but no build path reads it today — a label, not a switch: Build all sites uses containers whenever a container engine answers, and builds serially on the host when none does, in either mode.", maxParallelBuilds: "Cap on concurrent per-site container builds when Build all sites runs in" + - " containers (whenever a container engine answers, whatever `mode` says)." + + " containers (whenever a container engine answers)." + " Clamped to [1, " + "BUILD_MAX_PARALLEL_MAX].", dockerImage: @@ -1091,13 +1088,8 @@ export const BUILD_MAX_PARALLEL_MAX = 16; export const DEFAULT_BUILD_IMAGE = "yt-dlp-transcript-browser-build"; export const DEFAULT_BUILD_DOCKERFILE = "Dockerfile.build"; -export function isBuildMode(v: unknown): v is BuildMode { - return v === "basic" || v === "docker"; -} - export function defaultBuildPipeline(): BuildPipelineSettings { return { - mode: "basic", maxParallelBuilds: BUILD_MAX_PARALLEL_DEFAULT, dockerImage: DEFAULT_BUILD_IMAGE, dockerfile: DEFAULT_BUILD_DOCKERFILE, @@ -1105,7 +1097,8 @@ export function defaultBuildPipeline(): BuildPipelineSettings { } // Coerce a raw settings.buildPipeline value into a clean BuildPipelineSettings, -// falling back to defaults for missing/ill-typed fields. +// falling back to defaults for missing/ill-typed fields. Built from the three +// known fields only, so a retired key (the old `mode`) never survives. export function sanitizeBuildPipeline(value: unknown): BuildPipelineSettings { const d = defaultBuildPipeline(); if (!value || typeof value !== "object") return d; @@ -1119,7 +1112,6 @@ export function sanitizeBuildPipeline(value: unknown): BuildPipelineSettings { ? r.dockerfile.trim() : d.dockerfile; return { - mode: isBuildMode(r.mode) ? r.mode : d.mode, maxParallelBuilds: clampPositiveInt( r.maxParallelBuilds, d.maxParallelBuilds, @@ -1612,7 +1604,7 @@ export const siteSettingsSchema = z.object({ "Where a channel's downloaded media goes when it is relocated off the corpus disk. A DEFAULT ONLY: the relocate controller never reads it and always takes an explicit root, so this is the value the per-channel Storage panel prefills and the /channels bulk move falls back to. Blank = no default. See StorageSettings.", ), buildPipeline: settingsField((v): BuildPipelineSettings => sanitizeBuildPipeline(v)).describe( - "The build pipeline's settings. A single site builds in the shared export/ tree, serialized on one queue. Build all sites (and Build & deploy all) builds every site at once, each in its own container (Dockerfile.build, the image and maxParallelBuilds below), then deploys them serially, whenever a container engine answers, and serially on the host when none does — see PUBLISH.md. `mode` is persisted and shown on /sites, but no build path reads it today: it is a label, not a switch.", + "The build pipeline's settings. A single site builds in the shared export/ tree, serialized on one queue. Build all sites (and Build & deploy all) builds every site at once, each in its own container (Dockerfile.build, the image and maxParallelBuilds below), then deploys them serially, whenever a container engine answers, and serially on the host when none does — see PUBLISH.md. There is no mode switch; a `mode` key left in an older file is dropped on the next save.", ), digest: settingsField((v): DigestSettings => sanitizeDigest(v)).describe( "AI digest generation (chapters + topic tags over the existing transcripts). Local-first: the metered lane is off by default. See DigestSettings.", diff --git a/common/lib/settingsWrite.test.ts b/common/lib/settingsWrite.test.ts @@ -92,3 +92,32 @@ test("a held lane survives its own round trip", async () => { assert.equal(isGateHeld(back, "digest"), true); assert.equal(isGateHeld(back, "backfill"), false); }); + +// The retired `buildPipeline.mode` (release 11, follow-up O6c): a file on disk +// that still says it loads without error, and the next save drops it while +// keeping the three real fields. +test("a settings.json with the retired buildPipeline.mode loads, and a save drops the key", async () => { + const { writeFileSync } = await import("node:fs"); + const { getSettings } = await import("./settings"); + writeFileSync( + process.env.SETTINGS_FILE!, + JSON.stringify({ + buildPipeline: { mode: "basic", maxParallelBuilds: 3, dockerImage: "kept-image", dockerfile: "Dockerfile.build" }, + }), + ); + const loaded = getSettings(); + assert.equal("mode" in loaded.buildPipeline, false); + assert.equal(loaded.buildPipeline.maxParallelBuilds, 3); + assert.equal(loaded.buildPipeline.dockerImage, "kept-image"); + + await writeSettings(loaded); + const onDisk = JSON.parse(readFileSync(process.env.SETTINGS_FILE!, "utf8")) as { + buildPipeline: Record<string, unknown>; + }; + assert.equal("mode" in onDisk.buildPipeline, false); + assert.deepEqual(onDisk.buildPipeline, { + maxParallelBuilds: 3, + dockerImage: "kept-image", + dockerfile: "Dockerfile.build", + }); +}); diff --git a/common/publish/build.ts b/common/publish/build.ts @@ -375,7 +375,7 @@ export async function runDeployIntoLog( } // --------------------------------------------------------------------------- -// Docker export pipeline (buildPipeline.mode = "docker") +// Docker export pipeline (Build all, whenever a container engine answers) // // Three ordered phases (see PUBLISH.md, "Building every site in containers"): // A) HOST, serial: build:data (shared LMDB + .export-index) then build:archives diff --git a/editor/app/settings/actions.ts b/editor/app/settings/actions.ts @@ -6,7 +6,6 @@ import { AUTO_REFRESH_INTERVAL_MIN_SECONDS, DEFAULT_REPORT_DEBOUNCE_PRESET, defaultBuildPipeline, - isBuildMode, isReportDebouncePreset, MIN_FREE_DISK_GB_MAX, RESUME_MARGIN_GB_DEFAULT, @@ -180,12 +179,9 @@ export async function saveSettingsAction( } // Build pipeline. Values are clamped/coerced by sanitizeBuildPipeline inside - // the settings schema on save, so we only read the form here (NaN/blank → default). The - // deploy-page toggle also writes `mode`; whichever saves last wins. + // the settings schema on save, so we only read the form here (NaN/blank → default). const dB = defaultBuildPipeline(); - const buildModeRaw = String(formData.get("buildMode") ?? "").trim(); const buildPipeline = { - mode: isBuildMode(buildModeRaw) ? buildModeRaw : dB.mode, maxParallelBuilds: Number.parseInt( String(formData.get("maxParallelBuilds") ?? "").trim(), 10, diff --git a/editor/app/settings/components/SettingsForm.tsx b/editor/app/settings/components/SettingsForm.tsx @@ -302,30 +302,14 @@ export function SettingsForm({ initial }: Props) { queue. <strong>Build all sites</strong> builds every site at once, each in its own container (capped by Max parallel builds), then deploys them one by one, whenever a container engine answers, and serially on the - host when none does. The mode below is a label for now: no build reads - it. See PUBLISH.md. It can also be toggled on the{" "} - <a href="/sites" className="underline"> - Sites - </a>{" "} - page. + host when none does. See PUBLISH.md. </p> - <label className="flex flex-col gap-1 text-sm"> - <span className="font-medium">Build mode</span> - <select - name="buildMode" - defaultValue={initial.buildPipeline.mode} - className="rounded border border-border bg-card px-2 py-1 text-sm" - > - <option value="basic">Basic (a label for now)</option> - <option value="docker">Docker (a label for now)</option> - </select> - </label> <Field - label="Max parallel builds (Docker mode)" + label="Max parallel builds" name="maxParallelBuilds" defaultValue={String(initial.buildPipeline.maxParallelBuilds)} type="number" - hint="Cap on concurrent per-site container builds (1–16) when Build all sites runs in containers — whenever a container engine answers, whichever mode is set." + hint="Cap on concurrent per-site container builds (1–16) when Build all sites runs in containers — whenever a container engine answers." /> <Field label="Docker image tag" diff --git a/editor/app/sites/[siteId]/publish/page.tsx b/editor/app/sites/[siteId]/publish/page.tsx @@ -1,7 +1,6 @@ import { statSync } from "node:fs"; import path from "node:path"; import type { Metadata } from "next"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; @@ -54,13 +53,6 @@ export default async function SitePublishPage({ ? new Date(lastDeployedAt).toLocaleString() : "never"} </p> - <p className="text-sm text-muted-foreground mt-1"> - Build mode (Basic or Docker) is set on the{" "} - <Link className="underline" href="/sites"> - Sites - </Link>{" "} - page. - </p> </div> <BuildDeployButton siteId={siteId} diff --git a/editor/app/sites/components/BuildAllSitesButton.tsx b/editor/app/sites/components/BuildAllSitesButton.tsx @@ -10,9 +10,8 @@ import { JobLane } from "./JobLane"; type Lane = { deploy: boolean; skipArchives: boolean; key: number }; // One-click orchestrated pipeline over EVERY site as a SINGLE managed job (one -// log, one Cancel): whenever a container engine answers — whatever -// buildPipeline.mode says, which nothing reads — it runs the shared data phase + -// archive warm once, fans the per-site builds out in parallel (capped by +// log, one Cancel): whenever a container engine answers it runs the shared +// data phase + archive warm once, fans the per-site builds out in parallel (capped by // maxParallelBuilds), then deploys the built sites serially. Without one it falls // back to a serial host build+deploy. Distinct from the per-site panel below, // which launches one separate job per selected site. @@ -56,8 +55,7 @@ export function BuildAllSitesButton() { </label> </div> <p className="text-xs text-muted-foreground"> - When a container engine answers (whichever build mode is set): the shared - data phase runs once, per-site builds run in parallel in containers, then + When a container engine answers: the shared data phase runs once, per-site builds run in parallel in containers, then deploys run serially. With none, a serial host build and deploy, one site at a time. </p> diff --git a/editor/app/sites/components/BuildModeToggle.tsx b/editor/app/sites/components/BuildModeToggle.tsx @@ -1,66 +0,0 @@ -"use client"; - -import { useState, useTransition } from "react"; -import type { BuildMode } from "yt-dlp-transcript-common/lib/settings"; -import { setBuildModeAction } from "../lib/buildModeAction"; - -// Segmented Basic | Docker control. Persists the choice (setBuildModeAction → -// settings.json `buildPipeline.mode`). TODAY IT IS A LABEL: no build path reads -// it — Build all / Build & deploy all use containers whenever `docker version` -// answers (buildAction.ts, dockerAvailable) — and the note below says so rather -// than promising a switch. -export function BuildModeToggle({ mode: initialMode }: { mode: BuildMode }) { - const [mode, setMode] = useState<BuildMode>(initialMode); - const [pending, startTransition] = useTransition(); - - function choose(next: BuildMode) { - if (next === mode || pending) return; - setMode(next); - startTransition(async () => { - const res = await setBuildModeAction(next); - if (!res.ok) setMode(initialMode); - }); - } - - const options: { value: BuildMode; label: string }[] = [ - { value: "basic", label: "Basic" }, - { value: "docker", label: "Docker" }, - ]; - - return ( - <div className="flex flex-col gap-1.5"> - <span className="text-xs font-medium text-muted-foreground">Build mode</span> - <div - role="group" - aria-label="Build mode" - className="inline-flex w-fit rounded-md border border-border overflow-hidden text-sm" - > - {options.map((opt) => { - const active = mode === opt.value; - return ( - <button - key={opt.value} - type="button" - aria-pressed={active} - disabled={pending} - onClick={() => choose(opt.value)} - className={ - "px-3 py-1.5 font-medium transition-colors disabled:opacity-60 " + - (active - ? "bg-primary text-primary-foreground" - : "bg-transparent text-muted-foreground hover:bg-muted") - } - > - {opt.label} - </button> - ); - })} - </div> - <span className="text-xs text-muted-foreground"> - The mode is a label for now: Build all sites uses containers whenever a - container engine answers, and builds serially on the host when none - does, in either mode. - </span> - </div> - ); -} diff --git a/editor/app/sites/components/BuildSitesPanel.tsx b/editor/app/sites/components/BuildSitesPanel.tsx @@ -14,8 +14,8 @@ type Lane = { siteId: string; title: string; deploy: boolean; key: string }; // Batch build (and optionally deploy) several sites at once. Each selected site // launches its own managed job, rendered as its own live JobLane. The jobs share -// the build/deploy queue and run one at a time (the export/ tree is shared), -// whichever build mode is set; "Build all sites" is the parallel path. +// the build/deploy queue and run one at a time (the export/ tree is shared); +// "Build all sites" is the parallel path. export function BuildSitesPanel({ sites }: { sites: SiteOption[] }) { const [selected, setSelected] = useState<Set<string>>(new Set()); const [deployAfter, setDeployAfter] = useState(false); @@ -120,9 +120,9 @@ export function BuildSitesPanel({ sites }: { sites: SiteOption[] }) { </p> )} <p className="text-xs text-muted-foreground"> - These run one at a time (the build output tree is shared), whichever - build mode is set. “Build all sites” above builds every site in - parallel, in containers, whenever a container engine answers. + These run one at a time (the build output tree is shared). “Build all + sites” above builds every site in parallel, in containers, whenever a + container engine answers. </p> {lanes && lanes.length > 0 && ( diff --git a/editor/app/sites/lib/buildAction.ts b/editor/app/sites/lib/buildAction.ts @@ -82,8 +82,8 @@ export async function buildExportAction( if (!id) { return { ok: false, error: "Select a site to build" }; } - // Run as a managed function (not a raw command) so the build can route on the - // persisted build mode (basic vs docker) via runBuildPhase, and so the same + // Run as a managed function (not a raw command) so the build goes through + // runBuildPhase (a single site always builds in export/), and so the same // child-streaming path is shared with build-then-deploy. Cancel still works: // the abort signal is forwarded to the child by runChildIntoLog. // `skipData` composes from the existing .export-index staging without diff --git a/editor/app/sites/lib/buildModeAction.ts b/editor/app/sites/lib/buildModeAction.ts @@ -1,24 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { - getSettings, - isBuildMode, - type BuildMode, -} from "yt-dlp-transcript-common/lib/settings"; -import { saveSettings } from "../../settings/saveSettings"; - -// Persist the build-mode choice (Basic vs Docker) from the family page (/sites) -// so it becomes the default for every subsequent build. Only the mode is touched -// here; the image/concurrency knobs are edited on the Settings page. -// writeSettings re-sanitizes the whole object regardless. -export async function setBuildModeAction( - mode: BuildMode, -): Promise<{ ok: boolean }> { - if (!isBuildMode(mode)) return { ok: false }; - const settings = getSettings(); - // One key of one block: saveSettings merges it over the stored pipeline. - await saveSettings({ buildPipeline: { ...settings.buildPipeline, mode } }); - revalidatePath("/sites"); - return { ok: true }; -} diff --git a/editor/app/sites/page.tsx b/editor/app/sites/page.tsx @@ -10,7 +10,6 @@ import { import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { builtHomepageAt } from "yt-dlp-transcript-common/lib/builtExport"; import { getHomepageConfig } from "yt-dlp-transcript-common/lib/homepage"; -import { getSettings } from "yt-dlp-transcript-common/lib/settings"; import { listSites } from "yt-dlp-transcript-common/lib/site"; import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; import { @@ -21,7 +20,6 @@ import { liveJobRows } from "../jobs/active/buildActiveJobs"; import { RunningJobsList } from "../jobs/components/RunningJobsList"; import { BuildAllSitesButton } from "./components/BuildAllSitesButton"; import { BuildButtons } from "./components/BuildButtons"; -import { BuildModeToggle } from "./components/BuildModeToggle"; import { BuildSitesPanel } from "./components/BuildSitesPanel"; import { CutReleaseForm } from "./components/CutReleaseForm"; import { HomepageBuildButtons } from "./components/HomepageBuildButtons"; @@ -77,7 +75,6 @@ export default async function SitesPage() { const suggestedVersion = suggestNextVersion(lastVersion); const unreleased = source ? extractUnreleasedSection(source) : ""; const hasUnreleased = unreleased.length > 0; - const buildMode = getSettings().buildPipeline.mode; const existingQueues = getRegistry().activeQueueNames(); // Through the one builder, so this list has the same progress bars /jobs does // (it used to drop `progress`, `tasks`, `drainable` and the reorder bounds). @@ -160,9 +157,9 @@ export default async function SitesPage() { /> </section> - {/* Batch: build (and optionally deploy) several sites at once. The - build mode toggle sits here, and says it is a label: Build all fans - out in containers whenever an engine answers, whatever it says. */} + {/* Batch: build (and optionally deploy) several sites at once. Build all + fans out in containers whenever an engine answers; there is no mode + switch (dropped 2026-09-28, O6c). */} <section className="flex flex-col gap-3 border-t border-border pt-6"> <div> <h2 className="text-lg font-semibold">Build all sites</h2> @@ -172,7 +169,6 @@ export default async function SitesPage() { answers), then deploys run serially. </p> </div> - <BuildModeToggle mode={buildMode} /> <BuildAllSitesButton /> <div className="mt-4"> diff --git a/editor/e2e/deploy-page.spec.ts b/editor/e2e/deploy-page.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "@playwright/test"; import type { Page } from "@playwright/test"; -import { resetData, writeSite, readJson } from "./helpers"; +import { resetData, writeSite } from "./helpers"; // The "pick specific sites" panel (BuildSitesPanel). /sites renders two // controls named "Deploy after build" — one here, one in BuildAllSitesButton — @@ -68,59 +68,18 @@ test("Build & deploy is enabled only when the active site has a Cloudflare proje ).toBeVisible(); }); -test("build-mode toggle persists the choice and says the mode is a label", async ({ +// The Basic | Docker toggle was a label nothing read, and it is gone (release +// 11, follow-up O6c): Build all uses containers whenever an engine answers. +test("/sites has no build-mode toggle, and Build all says when it uses containers", async ({ page, }) => { await writeSite("testsite", { cloudflareProject: "proj" }); await page.goto("/sites"); - - const group = page.getByRole("group", { name: "Build mode" }); - await expect(group.getByRole("button", { name: "Basic" })).toHaveAttribute( - "aria-pressed", - "true", - ); - - // Retry the click until the toggle actually flips. A click dispatched before - // React hydrates fires NOTHING — no handler, no request, no error — so a bare - // click().then(assert) is a coin flip whenever hydration lags, which is what - // happens under the sharded route's concurrent containers (seen as - // aria-pressed still "false" 5s after the click). Same idiom as - // channel-site-membership.spec's setSiteChecked. - const docker = group.getByRole("button", { name: "Docker" }); - await expect(async () => { - await docker.click(); - await expect(docker).toHaveAttribute("aria-pressed", "true", { - timeout: 1_000, - }); - }).toPass({ timeout: 15_000 }); - await expect(page.getByText(/The mode is a label for now/i)).toBeVisible(); - - // Wait for the choice to actually reach settings.json before reloading. - // BuildModeToggle updates its own state OPTIMISTICALLY — setMode(next) flips - // aria-pressed immediately and only then does startTransition run - // setBuildModeAction — so the assertions above prove nothing about - // persistence. Reloading straight after them races the write: on a fast host - // the write wins, in a loaded container it does not, which is why this test - // passed under `pnpm e2e` and failed under `pnpm e2e:sharded`. - await expect - .poll( - async () => - ( - await readJson<{ buildPipeline?: { mode?: string } }>( - "test-settings.json", - ) - ).buildPipeline?.mode, - { timeout: 10_000 }, - ) - .toBe("docker"); - - // Persisted: reload and the toggle is still on Docker. - await page.goto("/sites"); + await expect(page.getByRole("heading", { name: "Build all sites" })).toBeVisible(); + await expect(page.getByRole("group", { name: "Build mode" })).toHaveCount(0); await expect( - page - .getByRole("group", { name: "Build mode" }) - .getByRole("button", { name: "Docker" }), - ).toHaveAttribute("aria-pressed", "true"); + page.getByText(/When a container engine answers: the shared data phase runs once/i), + ).toBeVisible(); }); test("batch panel: selecting sites enables the launch button and reflects deploy intent", async ({ diff --git a/editor/e2e/settings.spec.ts b/editor/e2e/settings.spec.ts @@ -66,11 +66,13 @@ test("rejects out-of-range maxTranscriptPageBytes", async ({ page }) => { ); }); -test("saves build pipeline settings (mode, concurrency, image)", async ({ +test("saves build pipeline settings (concurrency, image), and has no mode", async ({ page, }) => { await page.goto("/settings"); - await page.getByLabel("Build mode").selectOption("docker"); + // The Basic | Docker select was a label nothing read (dropped in release 11, + // follow-up O6c). + await expect(page.getByLabel("Build mode")).toHaveCount(0); await page.getByLabel(/max parallel builds/i).fill("4"); await page.getByLabel(/docker image tag/i).fill("my-build-image"); await page.getByRole("button", { name: /save settings/i }).click(); @@ -79,21 +81,9 @@ test("saves build pipeline settings (mode, concurrency, image)", async ({ ).toBeVisible(); const saved = await readJson<{ - buildPipeline?: { - mode: string; - maxParallelBuilds: number; - dockerImage: string; - }; + buildPipeline?: Record<string, unknown>; }>("test-settings.json"); - expect(saved.buildPipeline?.mode).toBe("docker"); expect(saved.buildPipeline?.maxParallelBuilds).toBe(4); expect(saved.buildPipeline?.dockerImage).toBe("my-build-image"); - - // The Sites page reflects the persisted mode. - await page.goto("/sites"); - await expect( - page - .getByRole("group", { name: "Build mode" }) - .getByRole("button", { name: "Docker" }), - ).toHaveAttribute("aria-pressed", "true"); + expect(saved.buildPipeline && "mode" in saved.buildPipeline).toBe(false); }); diff --git a/settings.json.example b/settings.json.example @@ -132,7 +132,6 @@ "defaultLocationId": "" }, "buildPipeline": { - "mode": "basic", "maxParallelBuilds": 2, "dockerImage": "yt-dlp-transcript-browser-build", "dockerfile": "Dockerfile.build"