Archilyzer · Source

archilyzer

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

commit 4e7d84fe910e3e4707c274b47f164a1f0d20ec15
parent 413b319e9981accde7e636d476acc509c0ca2a5f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 17 Sep 2026 15:01:38 -0400

e2e: a fake disk that moves, and the one click that follows it

Two fake bins on the pattern of the other eight, driven by one control file
`test-transcripts/.fake-findmnt.json`. fake-findmnt honours exactly the three
call shapes the probe makes — `-J -T <path>` for "what mount is this path
on", `-rn -S UUID=<u> -o TARGET` for "where is that volume now", and
`--fstab -S UUID=<u>` whose EXIT 1 is the answer "not in fstab", not a
failure. With no control file it reports nothing mounted, which is what
resetData() leaves behind and what every unrelated spec sees.

The spec writes that file to say "the platter is now mounted over there" —
the only way to exercise `mounted-elsewhere` without plugging a disk into the
machine. Then it moves the directory, refreshes, clicks once, and asserts the
things that are not about re-pointing at all: readlink, config.dataDir, and
the transcript route serving the same URL it always did.

clickUntil is in there for hydration, and says so: Playwright's actionability
check is satisfied by the button, not by the listener React has yet to attach,
and a Refresh is idempotent enough to retry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/storage/components/StorageLocationsTable.tsx | 7+++++--
Aeditor/e2e/fixtures/bin/fake-findmnt.mjs | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeditor/e2e/fixtures/bin/fake-udisksctl.mjs | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeditor/e2e/storage-locations.spec.ts | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meditor/package.json | 4++--
5 files changed, 466 insertions(+), 4 deletions(-)

diff --git a/editor/app/storage/components/StorageLocationsTable.tsx b/editor/app/storage/components/StorageLocationsTable.tsx @@ -81,13 +81,16 @@ export function StorageLocationsTable({ payload }: { payload: StorageRowsPayload /> ) : ( <div> + {/* The DISCLOSURE, not the submit. `add storage location` is the + form's own button (LocationForm), and two controls sharing one + accessible name is a screen reader reading the same sentence + for two different acts. */} <button type="button" - aria-label="add storage location" onClick={() => setAdding(true)} className="px-3 py-1.5 rounded-md border border-border text-sm font-medium" > - Add location + New location </button> </div> )} diff --git a/editor/e2e/fixtures/bin/fake-findmnt.mjs b/editor/e2e/fixtures/bin/fake-findmnt.mjs @@ -0,0 +1,115 @@ +#!/usr/bin/env node +// A FAKE `findmnt`, driven by a control file the spec writes. +// +// `common/lib/storageVolumes.ts` asks the real binary exactly three questions, +// and this honours those three call shapes and nothing else: +// +// 1. findmnt -J -T <path> -o TARGET,SOURCE,FSTYPE,LABEL,UUID +// → {"filesystems":[{target,source,fstype,label,uuid}]} (what mount is +// this path on?) +// 2. findmnt -rn -S UUID=<u> -o TARGET +// → one mountpoint per line, exit 1 when the volume is not mounted +// 3. findmnt --fstab -S UUID=<u> +// → exit 1 MEANS "no such fstab entry", and the probe reads that exit +// code as an answer rather than as a failure +// +// THE CONTROL FILE IS OPTIONAL. With no `<TRANSCRIPTS_DIR>/.fake-findmnt.json` +// this reports nothing mounted and no fstab entries — which is what a spec that +// never touches storage should see, and what `resetData()` leaves behind when +// it wipes the tree. That "absent = knows nothing" default is deliberate: a +// fake that guessed would make every unrelated spec depend on this file. +// +// Shape: +// { +// "mounts": [ +// { "uuid": "...", "target": "/abs/mountpoint", +// "source": "/dev/fake1", "fstype": "ext4", "label": "platter" } +// ], +// "fstab": ["<uuid>", ...] +// } + +import { readFileSync } from "node:fs"; +import path from "node:path"; + +function control() { + const dir = process.env.TRANSCRIPTS_DIR; + if (!dir) return { mounts: [], fstab: [] }; + try { + const raw = readFileSync(path.join(dir, ".fake-findmnt.json"), "utf8"); + const parsed = JSON.parse(raw); + return { + mounts: Array.isArray(parsed.mounts) ? parsed.mounts : [], + fstab: Array.isArray(parsed.fstab) ? parsed.fstab : [], + }; + } catch { + return { mounts: [], fstab: [] }; + } +} + +const argv = process.argv.slice(2); + +function valueAfter(flag) { + const i = argv.indexOf(flag); + return i >= 0 && i + 1 < argv.length ? argv[i + 1] : null; +} + +const source = valueAfter("-S"); // "UUID=<u>" +const uuid = source && source.startsWith("UUID=") ? source.slice(5) : null; +const { mounts, fstab } = control(); + +// 3. --fstab: is this volume in /etc/fstab? Exit 1 is the answer "no". +if (argv.includes("--fstab")) { + if (uuid && fstab.includes(uuid)) { + process.stdout.write(`UUID=${uuid} /mnt/${uuid} ext4 defaults 0 2\n`); + process.exit(0); + } + process.exit(1); +} + +// 2. -S UUID=<u> -o TARGET: where is this volume mounted right now? +if (uuid) { + const hit = mounts.find((m) => m.uuid === uuid); + if (!hit || !hit.target) process.exit(1); + process.stdout.write(`${hit.target}\n`); + process.exit(0); +} + +// 1. -T <path>: which mount is this path on? Longest matching target wins, the +// same way the kernel resolves it. +if (argv.includes("-T")) { + const target = valueAfter("-T"); + const resolved = target ? path.resolve(target) : ""; + let best = null; + for (const m of mounts) { + if (!m.target) continue; + const mt = path.resolve(m.target); + const under = resolved === mt || resolved.startsWith(`${mt}/`); + if (!under) continue; + if (!best || path.resolve(best.target).length < mt.length) best = m; + } + if (!best) { + // Nothing claims it. The probe reads an empty filesystems array as + // "identity unknown", which is the right fail-open answer. + process.stdout.write(JSON.stringify({ filesystems: [] }) + "\n"); + process.exit(0); + } + process.stdout.write( + JSON.stringify({ + filesystems: [ + { + target: path.resolve(best.target), + source: best.source ?? "/dev/fake1", + fstype: best.fstype ?? "ext4", + label: best.label ?? "", + uuid: best.uuid ?? "", + }, + ], + }) + "\n", + ); + process.exit(0); +} + +// Anything else (including --version) succeeds quietly: nothing in the probe +// asks it, and a fake that failed loudly on an unknown shape would turn a new +// call site into a mysterious "identity unknown" rather than a clear one. +process.exit(0); diff --git a/editor/e2e/fixtures/bin/fake-udisksctl.mjs b/editor/e2e/fixtures/bin/fake-udisksctl.mjs @@ -0,0 +1,65 @@ +#!/usr/bin/env node +// A FAKE `udisksctl`, driven by the same control file as fake-findmnt. +// +// Two call shapes, both from `common/lib/storageVolumes.ts`: +// +// 1. udisksctl --version → the availability probe (memoised) +// 2. udisksctl mount -b /dev/disk/by-uuid/<uuid> +// → "Mounted /dev/fake1 at <target>." on success; a non-zero exit with +// the daemon's words on stderr otherwise (a polkit denial is the +// expected real-world failure, and the probe surfaces it verbatim) +// +// `mountable` in `<TRANSCRIPTS_DIR>/.fake-findmnt.json` maps uuid → the +// mountpoint a mount would produce, and mounting ADDS the volume to `mounts` +// so a following findmnt sees it. With no control file nothing is mountable, +// which is what `resetData()` leaves behind. + +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +const argv = process.argv.slice(2); + +if (argv.includes("--version")) { + process.stdout.write("udisksctl 2.10.1 (fake)\n"); + process.exit(0); +} + +const controlPath = process.env.TRANSCRIPTS_DIR + ? path.join(process.env.TRANSCRIPTS_DIR, ".fake-findmnt.json") + : null; + +function read() { + if (!controlPath) return null; + try { + return JSON.parse(readFileSync(controlPath, "utf8")); + } catch { + return null; + } +} + +if (argv[0] === "mount") { + const i = argv.indexOf("-b"); + const dev = i >= 0 && i + 1 < argv.length ? argv[i + 1] : ""; + const uuid = path.basename(dev); + const control = read(); + const target = control?.mountable?.[uuid]; + if (!target) { + process.stderr.write( + `Error mounting ${dev}: GDBus.Error:org.freedesktop.UDisks2.Error.Failed: ` + + `No such device (fake)\n`, + ); + process.exit(1); + } + const mounts = Array.isArray(control.mounts) ? control.mounts : []; + if (!mounts.some((m) => m.uuid === uuid)) { + mounts.push({ uuid, target, source: "/dev/fake1", fstype: "ext4" }); + writeFileSync( + controlPath, + JSON.stringify({ ...control, mounts }, null, 2) + "\n", + ); + } + process.stdout.write(`Mounted /dev/fake1 at ${target}.\n`); + process.exit(0); +} + +process.exit(0); diff --git a/editor/e2e/storage-locations.spec.ts b/editor/e2e/storage-locations.spec.ts @@ -0,0 +1,279 @@ +import { mkdir, readlink, rename, rm, symlink, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { test, expect, type Page } from "@playwright/test"; +import { baseUrl } from "./baseUrl"; +import { + pathExists, + readJson, + resetData, + resolvePath, + writeSettings, +} from "./helpers"; + +// STORAGE LOCATIONS: the page, and the one click that follows a disk. +// +// The claim under test is the one the whole design rests on: when a drive comes +// back at a different mountpoint, ONE button makes every channel on it readable +// again, and it does so WITHOUT MOVING A BYTE. So the assertions after the +// re-point are deliberately not about the re-point — they are `readlink`, +// `config.dataDir` and the transcript route serving the same URL it always did, +// with the media now reached through a different path entirely. +// +// THE DISK IS A FAKE BINARY. `FINDMNT_BIN` and `UDISKSCTL_BIN` point at +// `e2e/fixtures/bin/fake-{findmnt,udisksctl}.mjs` (see editor/package.json), and +// both read `test-transcripts/.fake-findmnt.json`. Writing that file is how this +// spec says "the platter is now mounted over there" — the only way to exercise +// `mounted-elsewhere` without plugging a disk into the CI machine. `resetData()` +// wipes the tree, so a spec that never writes it sees "nothing is mounted". + +const SLUG = "test-youtube"; +const VIDEO = "20240101_test1234567"; +const UUID = "e2e-platter-uuid"; + +const channelDir = () => resolvePath(`test-transcripts/channels/${SLUG}`); +const dataLink = () => join(channelDir(), "data"); +const controlFile = () => resolvePath("test-transcripts/.fake-findmnt.json"); + +async function writeControl(control: { + mounts?: Array<Record<string, unknown>>; + fstab?: string[]; + mountable?: Record<string, string>; +}): Promise<void> { + await writeFile(controlFile(), JSON.stringify(control, null, 2) + "\n"); + await fetch(`${baseUrl}/api/test/invalidate-cache`).catch(() => {}); +} + +// Put the fixture channel's media on `root`, the way a finished relocation +// leaves it: the real directory on the "drive", an absolute symlink at +// channels/<slug>/data, and config.dataDir naming the target. Built directly +// rather than by running a move — what is under test here is the re-point, and +// a real rsync first would buy nothing but a minute. +async function relocateOnDisk(root: string): Promise<string> { + const target = join(root, SLUG, "data"); + await mkdir(join(root, SLUG), { recursive: true }); + await rename(dataLink(), target); + await symlink(target, dataLink()); + const config = await readJson<Record<string, unknown>>( + `test-transcripts/channels/${SLUG}/config.json`, + ); + await writeFile( + join(channelDir(), "config.json"), + JSON.stringify({ ...config, dataDir: target }, null, 2) + "\n", + ); + await fetch(`${baseUrl}/api/test/invalidate-cache`).catch(() => {}); + return target; +} + +function row(page: Page, id: string) { + return page.getByLabel(`storage location: ${id}`); +} + +// CLICK UNTIL IT TOOK, and the reason is hydration, not flakiness-by-vibes. +// Every control on this page is a React handler attached after the server HTML +// arrives; Playwright's actionability check is satisfied by the BUTTON, not by +// the listener, so a click landing in that window is silently dropped. These +// actions are all idempotent (a Refresh re-probes, a disclosure re-opens), so +// retrying until the expected consequence appears is both safe and the honest +// way to wait for a listener the DOM cannot advertise. +async function clickUntil( + button: ReturnType<Page["getByLabel"]>, + settled: () => Promise<boolean>, + timeout = 30_000, +): Promise<void> { + await expect + .poll( + async () => { + if (await settled()) return true; + await button.click({ timeout: 5_000 }).catch(() => {}); + return settled(); + }, + { timeout, intervals: [250, 500, 1_000, 1_000, 2_000] }, + ) + .toBe(true); +} + +test("the page lists a location with its status, counts and refusals", async ({ + page, +}, testInfo) => { + test.setTimeout(90_000); + await resetData("one-youtube-channel-with-data"); + const root = testInfo.outputPath("platter"); + await mkdir(root, { recursive: true }); + await relocateOnDisk(root); + await writeSettings({ + adminTitle: "Test Admin", + storage: { + locations: [{ id: "cold", label: "Cold", root, autoRepoint: false }], + defaultLocationId: "cold", + }, + }); + + await page.goto("/storage"); + const locations = page.getByLabel("storage locations"); + await expect(locations).toBeVisible(); + const cold = row(page, "cold"); + await expect(cold).toBeVisible(); + await expect(cold.getByLabel("location status")).toHaveText("Available"); + // The root exists and the channel's link resolves through it. + await expect(cold.getByLabel("location channels")).toHaveText( + "1 ok / 0 unreachable / 0 moving", + ); + + // DELETE IS REFUSED WHILE ANYBODY LIVES THERE. Deleting the location moves no + // bytes — which is exactly why it is refused: it would erase the only record + // of which disk that channel's absolute dataDir belongs to. + await expect(cold.getByLabel("delete cold")).toBeDisabled(); + await expect(cold.getByLabel("delete withheld")).toContainText( + "1 channel(s) still have their media under", + ); + + // ...and so is a re-point, for the honest reason: nothing is wrong. + await expect( + page.getByRole("button", { name: /^Re-point to / }), + ).toHaveCount(0); + + // --- adding one --------------------------------------------------------- + const second = testInfo.outputPath("warm"); + await mkdir(second, { recursive: true }); + await clickUntil( + page.getByRole("button", { name: "New location" }), + () => page.getByLabel("location id").isVisible(), + ); + await page.getByLabel("location id").fill("warm"); + await page.getByLabel("location label").fill("Warm"); + await page.getByLabel("location root").fill(second); + await page.getByLabel("auto re-point").check(); + await page.getByLabel("add storage location").click(); + + const warm = row(page, "warm"); + await expect(warm).toBeVisible({ timeout: 15_000 }); + await expect(warm.getByLabel("location status")).toHaveText("Available"); + // Nothing is on it, so this one CAN be deleted. + await expect(warm.getByLabel("delete warm")).toBeEnabled(); + await expect(warm.getByLabel("location channels")).toHaveText( + "0 ok / 0 unreachable / 0 moving", + ); + + // It landed in settings.json, opt-in included. + const settings = await readJson<{ + storage: { locations: Array<Record<string, unknown>> }; + }>("test-settings.json"); + const stored = settings.storage.locations.find((l) => l.id === "warm"); + expect(stored?.root).toBe(second); + expect(stored?.autoRepoint).toBe(true); +}); + +test("a volume that came up somewhere else is re-pointed in one click", async ({ + page, +}, testInfo) => { + test.setTimeout(120_000); + await resetData("one-youtube-channel-with-data"); + const rootA = testInfo.outputPath("mount-a"); + const rootB = testInfo.outputPath("mount-b"); + await mkdir(rootA, { recursive: true }); + await mkdir(rootB, { recursive: true }); + const targetA = await relocateOnDisk(rootA); + await writeSettings({ + adminTitle: "Test Admin", + storage: { + locations: [{ id: "cold", label: "Cold", root: rootA, autoRepoint: false }], + defaultLocationId: "cold", + }, + }); + // The platter, mounted where the location says it is. No fstab entry — which + // is the real machine's situation and is what the warning is for. + await writeControl({ + mounts: [{ uuid: UUID, target: rootA, fstype: "ext4", label: "platter" }], + fstab: [], + }); + + // --- learn the identity ------------------------------------------------- + await page.goto("/storage"); + const cold = row(page, "cold"); + await expect(cold.getByLabel("location status")).toHaveText("Available"); + await clickUntil(cold.getByLabel("refresh cold"), async () => + (await cold.getByLabel("location identity").textContent())?.includes(UUID) ?? + false, + ); + // A refresh writes IDENTITY and only identity — availability is never stored. + await expect + .poll(async () => { + const s = await readJson<{ + storage: { locations: Array<{ id: string; volume?: { uuid?: string } }> }; + }>("test-settings.json"); + return s.storage.locations.find((l) => l.id === "cold")?.volume?.uuid; + }, { timeout: 15_000 }) + .toBe(UUID); + await expect(cold.getByLabel("location warning")).toContainText("automount"); + + // --- the disk comes back somewhere else --------------------------------- + // The media moves with it, because it IS the disk. Nothing in the corpus + // changes: config.dataDir and the symlink still name the old mountpoint, + // which is precisely the breakage this page exists for. + await rename(join(rootA, SLUG), join(rootB, SLUG)); + await rm(rootA, { recursive: true, force: true }); + await writeControl({ + mounts: [{ uuid: UUID, target: rootB, fstype: "ext4", label: "platter" }], + fstab: [], + }); + expect(await pathExists(`test-transcripts/channels/${SLUG}/data`)).toBe(true); + + await page.goto("/storage"); + // The probe memo is ten seconds wide and keyed by id+root, neither of which + // changed — so the page's own render can legitimately answer from the probe + // taken before the disk moved. Refresh is what bypasses it, and that is the + // whole point of the button. + await clickUntil( + cold.getByLabel("refresh cold"), + async () => + (await cold.getByLabel("location status").textContent())?.trim() === + "Mounted elsewhere", + ); + await expect(cold.getByLabel("location channels")).toHaveText( + "0 ok / 1 unreachable / 0 moving", + ); + + // --- one click ---------------------------------------------------------- + const repoint = cold.getByRole("button", { name: `Re-point to ${rootB}` }); + await expect(repoint).toBeEnabled({ timeout: 15_000 }); + await repoint.click(); + await expect(page.getByLabel("Re-point output")).toContainText("Done.", { + timeout: 60_000, + }); + + // The link and the config now name the new mountpoint... + const targetB = join(rootB, SLUG, "data"); + expect(targetA).not.toBe(targetB); + await expect + .poll(async () => readlink(dataLink()), { timeout: 30_000 }) + .toBe(targetB); + expect( + ( + await readJson<{ dataDir?: string }>( + `test-transcripts/channels/${SLUG}/config.json`, + ) + ).dataDir, + ).toBe(targetB); + // ...and so does the location. + const settings = await readJson<{ + storage: { locations: Array<{ id: string; root: string }> }; + }>("test-settings.json"); + expect(settings.storage.locations.find((l) => l.id === "cold")?.root).toBe( + rootB, + ); + + // NOTHING ELSE NOTICED. The per-file route joins channelsDir/<slug>/data/<id> + // exactly as it always has and has no idea any of this happened. + const file = await page.request.get( + `${baseUrl}/api/channels/${SLUG}/videos/${VIDEO}/files/transcript.en.vtt`, + ); + expect(file.status()).toBe(200); + expect(await file.text()).toContain("WEBVTT"); + + // And the page agrees: the channel is reachable again. + await page.goto("/storage"); + await expect(cold.getByLabel("location status")).toHaveText("Available"); + await expect(cold.getByLabel("location channels")).toHaveText( + "1 ok / 0 unreachable / 0 moving", + ); +}); diff --git a/editor/package.json b/editor/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "dev": "next dev --port ${EDITOR_PORT:-3001}", - "dev:test": "WORKER_TOKEN=test-worker-token TRANSCRIPTS_DIR=$(pwd)/test-transcripts EXPORT_PUBLIC_DIR=$(pwd)/test-transcripts/.export-public SETTINGS_FILE=$(pwd)/test-settings.json EDITOR_CHANGELOG_FILE=$(pwd)/test-changelog.md YTDLP_BIN=$(pwd)/e2e/fixtures/bin/fake-ytdlp.mjs GALLERY_DL_BIN=$(pwd)/e2e/fixtures/bin/fake-gallery-dl.mjs WHISPER_BIN=$(pwd)/e2e/fixtures/bin/fake-whisper.mjs WHISPER_MODEL=/dev/null CHOUGH_BIN=$(pwd)/e2e/fixtures/bin/fake-chough.mjs CHOUGH_MODEL=/dev/null PARAKEET_STITCH_BIN=$(pwd)/e2e/fixtures/bin/fake-parakeet-stitch.mjs PARAKEET_CLI=/dev/null PARAKEET_MODEL=/dev/null DIARIZE_BIN=$(pwd)/e2e/fixtures/bin/fake-diarize.mjs FFMPEG_BIN=$(pwd)/e2e/fixtures/bin/fake-ffmpeg.mjs FFPROBE_BIN=$(pwd)/e2e/fixtures/bin/fake-ffprobe.mjs OLLAMA_URL=http://127.0.0.1:${OLLAMA_STUB_PORT:-11435} CLAUDE_BIN=$(pwd)/e2e/fixtures/bin/fake-claude.mjs AUDIO_CHECK_INTERVAL_MS_OVERRIDE=300 AUDIO_CHECK_SIZE_GATE_OVERRIDE=4096 AUDIO_CHECK_INTERVAL_FLOOR_MS_OVERRIDE=50 AUDIO_CHECK_RECOVER_STEP_MS_OVERRIDE=100 AUDIO_CHECK_RECOVER_AFTER_OVERRIDE=2 next dev --port ${PORT:-3011}", - "start:test": "WORKER_TOKEN=test-worker-token TRANSCRIPTS_DIR=$(pwd)/test-transcripts EXPORT_PUBLIC_DIR=$(pwd)/test-transcripts/.export-public SETTINGS_FILE=$(pwd)/test-settings.json EDITOR_CHANGELOG_FILE=$(pwd)/test-changelog.md YTDLP_BIN=$(pwd)/e2e/fixtures/bin/fake-ytdlp.mjs GALLERY_DL_BIN=$(pwd)/e2e/fixtures/bin/fake-gallery-dl.mjs WHISPER_BIN=$(pwd)/e2e/fixtures/bin/fake-whisper.mjs WHISPER_MODEL=/dev/null CHOUGH_BIN=$(pwd)/e2e/fixtures/bin/fake-chough.mjs CHOUGH_MODEL=/dev/null PARAKEET_STITCH_BIN=$(pwd)/e2e/fixtures/bin/fake-parakeet-stitch.mjs PARAKEET_CLI=/dev/null PARAKEET_MODEL=/dev/null DIARIZE_BIN=$(pwd)/e2e/fixtures/bin/fake-diarize.mjs FFMPEG_BIN=$(pwd)/e2e/fixtures/bin/fake-ffmpeg.mjs FFPROBE_BIN=$(pwd)/e2e/fixtures/bin/fake-ffprobe.mjs OLLAMA_URL=http://127.0.0.1:${OLLAMA_STUB_PORT:-11435} CLAUDE_BIN=$(pwd)/e2e/fixtures/bin/fake-claude.mjs AUDIO_CHECK_INTERVAL_MS_OVERRIDE=300 AUDIO_CHECK_SIZE_GATE_OVERRIDE=4096 AUDIO_CHECK_INTERVAL_FLOOR_MS_OVERRIDE=50 AUDIO_CHECK_RECOVER_STEP_MS_OVERRIDE=100 AUDIO_CHECK_RECOVER_AFTER_OVERRIDE=2 next start --port ${PORT:-3011}", + "dev:test": "WORKER_TOKEN=test-worker-token TRANSCRIPTS_DIR=$(pwd)/test-transcripts EXPORT_PUBLIC_DIR=$(pwd)/test-transcripts/.export-public SETTINGS_FILE=$(pwd)/test-settings.json EDITOR_CHANGELOG_FILE=$(pwd)/test-changelog.md YTDLP_BIN=$(pwd)/e2e/fixtures/bin/fake-ytdlp.mjs GALLERY_DL_BIN=$(pwd)/e2e/fixtures/bin/fake-gallery-dl.mjs WHISPER_BIN=$(pwd)/e2e/fixtures/bin/fake-whisper.mjs WHISPER_MODEL=/dev/null CHOUGH_BIN=$(pwd)/e2e/fixtures/bin/fake-chough.mjs CHOUGH_MODEL=/dev/null PARAKEET_STITCH_BIN=$(pwd)/e2e/fixtures/bin/fake-parakeet-stitch.mjs PARAKEET_CLI=/dev/null PARAKEET_MODEL=/dev/null DIARIZE_BIN=$(pwd)/e2e/fixtures/bin/fake-diarize.mjs FFMPEG_BIN=$(pwd)/e2e/fixtures/bin/fake-ffmpeg.mjs FFPROBE_BIN=$(pwd)/e2e/fixtures/bin/fake-ffprobe.mjs OLLAMA_URL=http://127.0.0.1:${OLLAMA_STUB_PORT:-11435} CLAUDE_BIN=$(pwd)/e2e/fixtures/bin/fake-claude.mjs FINDMNT_BIN=$(pwd)/e2e/fixtures/bin/fake-findmnt.mjs UDISKSCTL_BIN=$(pwd)/e2e/fixtures/bin/fake-udisksctl.mjs AUDIO_CHECK_INTERVAL_MS_OVERRIDE=300 AUDIO_CHECK_SIZE_GATE_OVERRIDE=4096 AUDIO_CHECK_INTERVAL_FLOOR_MS_OVERRIDE=50 AUDIO_CHECK_RECOVER_STEP_MS_OVERRIDE=100 AUDIO_CHECK_RECOVER_AFTER_OVERRIDE=2 next dev --port ${PORT:-3011}", + "start:test": "WORKER_TOKEN=test-worker-token TRANSCRIPTS_DIR=$(pwd)/test-transcripts EXPORT_PUBLIC_DIR=$(pwd)/test-transcripts/.export-public SETTINGS_FILE=$(pwd)/test-settings.json EDITOR_CHANGELOG_FILE=$(pwd)/test-changelog.md YTDLP_BIN=$(pwd)/e2e/fixtures/bin/fake-ytdlp.mjs GALLERY_DL_BIN=$(pwd)/e2e/fixtures/bin/fake-gallery-dl.mjs WHISPER_BIN=$(pwd)/e2e/fixtures/bin/fake-whisper.mjs WHISPER_MODEL=/dev/null CHOUGH_BIN=$(pwd)/e2e/fixtures/bin/fake-chough.mjs CHOUGH_MODEL=/dev/null PARAKEET_STITCH_BIN=$(pwd)/e2e/fixtures/bin/fake-parakeet-stitch.mjs PARAKEET_CLI=/dev/null PARAKEET_MODEL=/dev/null DIARIZE_BIN=$(pwd)/e2e/fixtures/bin/fake-diarize.mjs FFMPEG_BIN=$(pwd)/e2e/fixtures/bin/fake-ffmpeg.mjs FFPROBE_BIN=$(pwd)/e2e/fixtures/bin/fake-ffprobe.mjs OLLAMA_URL=http://127.0.0.1:${OLLAMA_STUB_PORT:-11435} CLAUDE_BIN=$(pwd)/e2e/fixtures/bin/fake-claude.mjs FINDMNT_BIN=$(pwd)/e2e/fixtures/bin/fake-findmnt.mjs UDISKSCTL_BIN=$(pwd)/e2e/fixtures/bin/fake-udisksctl.mjs AUDIO_CHECK_INTERVAL_MS_OVERRIDE=300 AUDIO_CHECK_SIZE_GATE_OVERRIDE=4096 AUDIO_CHECK_INTERVAL_FLOOR_MS_OVERRIDE=50 AUDIO_CHECK_RECOVER_STEP_MS_OVERRIDE=100 AUDIO_CHECK_RECOVER_AFTER_OVERRIDE=2 next start --port ${PORT:-3011}", "build": "next build", "start": "next start --port ${EDITOR_PORT:-3001}", "lint": "eslint",