Archilyzer · Source

archilyzer

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

commit be311a8e4d9f6a6c691f6b9ea8240e0e35fd65ef
parent 73a5797e82b86bb248c6a12bc737da3387a74f14
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 31 Aug 2026 12:38:58 -0400

plans: the transcode removal planned

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

Diffstat:
Aplans/editor-transcode-removed.md | 541+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 541 insertions(+), 0 deletions(-)

diff --git a/plans/editor-transcode-removed.md b/plans/editor-transcode-removed.md @@ -0,0 +1,541 @@ +# The slice-5 leftovers, then the transcode operation is removed: it never fired in production + +## Context + +**Verified read-only against `f3b187a` (clean) on 2026-08-30/31**, the commit that closed +slice 5. Every file:line was re-read at this sha by the design pass; where the initial brief's +anchors or wording were off, a finding says so. The `duplicates-page` worktree exists (HEAD +`7baef40`, merge-base `43bb519`) — another session commits into this checkout. + +The IA docs' remaining editor item was "the transcode band" (`plans/editor-operations-ia.md:60`, +`STATE.md:328-331`): teach the snapshot writer a transcode population so `EXTERNAL_BAND_IDS` +could go to three. The operator's instinct — that the transcode feature was built for a bug +that never occurred — is confirmed by the census, and **the band question closes by deletion, +not by a snapshot-shape change**. + +**The production census** (read-only `jq` over `transcripts/channels/*/{snapshot,config}.json`; +nothing booted, nothing written): + +| fact | figure | +|---|---| +| channels / snapshots | **68 / 68** — every snapshot carries a `buckets.untranscoded` key | +| `generatedAt` | 44 on 2026-08-27, 19 on 08-30, 5 on 08-31 (UTC) | +| `buckets.untranscoded` non-empty | **0 of 68** | +| `buckets.multipleAudioFormats` non-empty | **0 of 68** | +| `cleanupBytes.foreignAudio > 0` | **0 of 68** | +| `failed-transcodings` files on disk | **2** (`cornbreadman` mtime Jun 29, `the-quartering-rumble` mtime May 16), **both 0 bytes** | +| channels passing `handling==="transcribe" && !!audioFormat` | **4**: cornbreadman, hasanabi, omnimirror, the-quartering-rumble (all mp3) | +| `keepLatest > 0` (forces `extractionMode: "app"`, `persistencePlan.ts:92-94`) | **2**: nuxanor-kick 1, quarteringvlogs 5 | + +**Transcode is three layers wearing one name.** (1) *Download-time extraction*: the app runs +ffmpeg itself (`transcodeAudio`, `common/controller/transcode.ts`) when +`extractionMode === "app"` — two callers, `audioCheckedDownload.ts:44,768` and +`downloadOneManaged.ts:30,224` — forced for keep-latest videos; **live in production, KEPT.** +(2) *The transcode OPERATION* (`70ed00f` 2026-05-19 "transcode failure handling"; half of +`bb380c6` 2026-06-18): a channel-page Transcode stage, a `failed-transcodings` ledger, four +job kinds, `buckets.untranscoded`, a "transcoded" dot and failed filter on the video list, a +registry entry carrying the catalog's only `appliesTo`, and `/operations/transcode` — built +for yt-dlp's `-x` step failing or a codec whisper could not read. Neither happened, and it +could not have mattered: `transcribeOne.ts:22-31` `resolveAudioFile` falls back to ANY real +audio file when `audio.<fmt>` is absent (strict is opt-in per manual batch, default false — +`whisperBatch.ts:73,99`). **DELETED.** (3) *Wrong-format audio cleanup* (the other half of +`bb380c6`): `multipleAudioFormats`/`foreignAudio`, both sweeps and their bulk actions. +**KEPT**, re-gated on `!!config.audioFormat`. + +**Decided (operator, 2026-08-30):** + +- **(A) The slice-5 leftovers ride first**, one commit: `measure-nav.mjs` names live routes + and warns on a 3xx; `BUILD_KINDS` is the Pool's eight; `EditorAliasesClient` always has a + site. +- **(B) Option "the operation".** DELETE layer 2 — the four controllers, four job kinds and + their actions/replay handlers, the stage everywhere it is wired, the registry entry and + (sole taker) `appliesTo`/`operationApplies`. KEEP `transcodeAudio` and the download path, + the per-file "Transcode <file> → <fmt>" rows on the video page (`transcodeAudioAction`, + the one escape hatch), both cleanup sweeps, `buckets.multipleAudioFormats`. +- **(C) `buckets.untranscoded` is kept and renamed `wrongFormatAudio`** — it is the orphan + sweep's list; its name presupposed a transcode. Old snapshots keep a stray key and read + `[]` until their next regen; **no regen tooling** (precedent `unified-ops-step-1.md:52-58`: + no snapshot schema version, per-field optionality; an offline writer would race + `snapshotScheduler`). Production has 0 in the bucket, so nothing rendered moves. +- **(D) `EXTERNAL_BAND_IDS` stays `["download","transcription"]` permanently** + (`buildBands.ts:163`; `buildBands.test.ts:243-246,322` keep pinning two). +- **(E) Stale `failed-transcodings` files stay on disk** (both empty); nothing reads them + after this. +- **Cadence:** Fable reviews; one Opus agent implements. Stage by explicit path; `git rm`; + e2e detached, once, at the end; never boot the editor against `transcripts/`. + +**What the census found — every place the obvious move cannot be made literally, and what +instead:** + +1. **The worker tag is on `/workers`, not `/settings`.** `workers/page.tsx:21-28` builds + `knownTags` from `operationCatalog().filter(o => o.scope === "video")`. The "transcode" + tag disappears with the catalog entry, no edit needed — FACTS.md:2240 names the wrong + page; the docs commit corrects it. +2. **`transcode-audio` is not a registered job kind.** Only `videoActions.ts:84` (the kept + per-file action) and `stageStatus.ts:119` (the stage map). Drop the map entry with the + stage; the job still shows in `RunningJobsList` and marks no stage as running — right, + there is no stage. +3. **`bulkVideoActions.ts:215-218` is Clear-failed-markers, not bulk delete.** The prune of + `failedTranscodingsFile` goes, `succeeded` becomes `trxn.pruned`, comment `:202-204` says + "the failed-transcriptions file". +4. **Editor before common, or tsc breaks mid-series.** Five editor files import the doomed + common modules. Commit 2 removes every editor reader; commit 3 deletes the common side. + Each commit passes all six tscs. +5. **The bucket rename crosses the package boundary and must be one commit** (commit 4: + writer + `normalizeBuckets` + the two readers). The e2e fixtures spelling + `untranscoded: []` are untyped JSON literals — **left alone**: `normalizeBuckets` picks + only keys it knows, exactly as production's stray key is ignored. +6. **The catalog count is pinned at eight.** `pauseGates.test.ts:104` `ids.length === 8`, + `:87-93` expects `transcode: null`; `pauseGates.ts:60` "all eight ids"; + `operations.test.ts:1222-1235` lists `"transcode"`. All go to seven. +7. **`jobKinds.test.ts:97` uses `transcode-failures` as its label-less example** → + `clear-failed-transcriptions` (replayable, no label, in neither `OLD_LABELS` nor + `ADDED_KINDS`). +8. **The "runner first" order in `pauseLaneFor` stays justified without naming transcode** — + it is a property of the catalog's shape (an external entry sharing a runner's queue with + no runner of its own must answer null). Same rewording for the four "external with no + runner" comments (`operations.ts:1422-1427`, `operations.test.ts:1246-1251`, + `operations/[id]/page.tsx:149-152`, `operations/lanes.ts:64-70`) and + `OperationDetail.tsx:137-139` ("transcode today"); `NoConsoleView` stays as the + fallthrough. This is what makes the `transcod` grep gate exact. +9. **`GROUP_STAGES` stays a `Record` of arrays** (`sync: []` still needs it); `media: + ["download"]`, paragraph rewritten around sync. +10. **The page's stage-order filter has nothing left to filter.** `page.tsx:256-261` and + `stageOrder.test.ts:28-39` both become the bare + `...OPERATION_GROUP_ORDER.flatMap((g) => GROUP_STAGES[g])`; the "without transcode" test + case (`:56-71`) goes. +11. **`?stage=transcode` bookmarks already have a rule** — unknown `?stage=` lands on the + overview at 200 (`channel-stage-selection.spec.ts:89`); the comment at `page.tsx:267-271` + gains transcode as its real example. +12. **`FlowStationId` is a key of four `Record`s** (`stationById`, `sidingsOf`, + `reachableInto`, `GAP_LABEL`) — all four lose their `transcode` key with the union member. +13. **The "Transcoded" dot was never the operation's fact** — `VideoPanel.tsx:233` + `transcoded={transcodeSources.length === 0}` is true of any `audio.*` file, so a video in + the channel's own format read "not transcoded". Nothing pins it. **Deleted** (`:233,535, + 540,555`); the strip is Downloaded · Transcribed. The per-file rows (`:119-126,169, + 202-205,304-318,763-821`) and `transcodeAudioAction` stay; `video-page.spec.ts:118-133, + 398-440` pin exactly those. +14. **`transcodeAudio` has TWO download-path callers** (`audioCheckedDownload.ts:768`, + `downloadOneManaged.ts:224` `finalizeAppExtraction`) — both KEEP, both in the grep gate's + allowed list. +15. **`operation-settings.spec.ts:238-239`** (the transcode goto + assertion) is deleted; + the `/operations/download` pair at `:240-241` keeps the same assertion on the same shape. +16. **`bulk-actions.spec.ts:176-206` is trimmed, not deleted** — the failed-transcoding half + goes, counts 2 → 1, the transcription half stays. +17. **`channelFlow.ts:153` `config` is an unused input already** — out of scope; do not "fix" + in passing. +18. **No doc outside `plans/` describes the Transcode stage.** README/SETUP/install.md say + ffmpeg does "audio transcode" — that is the download path and stays true. +19. **The `test-transcribe` fixture's `audio.m4a` files stay** (`audioFormat: "m4a"` — its + `untranscoded` bucket was always empty; `video-page`/`whisper`/`bulk-actions` all use the + files for kept features). +20. **`bb380c6` is two things** — the FACTS section says which half went. + +**Hazards carried through every commit:** + +- **Another session commits into this checkout.** `git status` before every commit; explicit + paths; never `git add -A`. The **`duplicates-page` worktree** edits `common/jobs/jobKinds.ts` + (its +30 hunk at `:251-290` vs our deletions at `:348-371` — same file, auto-merge needs a + human eye), adds `/duplicates` to `nav.ts` (main's `nav.test.ts` pins eleven; `/review` is + the folded slot the IA doc reserved), and still carries files main deleted + (`build/page.tsx` with its own `BUILD_KINDS`, `actionable/page.tsx`). Their merge; recorded + so nobody is surprised. +- **Playwright strict mode:** deletions only, no new locators. `"Select failed"` unchanged. +- **`stageStatus.ts` keeps `config`** (the configure stage reads it); only the + `operationApplies` call goes. **`whisperActions.ts` keeps** `AUDIO_FORMAT_VALUES`/ + `AudioFormat` and `clearFailedTranscriptions`; **`jobReplayRegistry.ts` keeps** + `AudioFormat`. +- **`grep` reads `common/controller/sweepRecency.ts` as binary** — identifier sweeps skip it; + trust tsc. +- **e2e detached once after commit 5; never against `transcripts/`.** + +## Step 0 — the plan on disk + +Write this file verbatim to `plans/editor-transcode-removed.md`; commit alone: +`plans: the transcode removal planned`. + +## Order: four code commits after the plan, then the docs + +1. **`editor: the slice-5 leftovers — the nav timer names live routes, the pool lists its + eight kinds, the aliases tab always has a site`** +2. **`channels: the transcode stage is gone`** +3. **`transcode: the operation is deleted — it never fired in production`** +4. **`snapshot: the untranscoded bucket is wrong-format audio`** +5. **`plans: the transcode operation is removed, and the docs say so`** + +Gates after each of 1–4: `pnpm -C <pkg> exec tsc --noEmit` for `common editor export homepage +umtool mcp`; `pnpm -C common test`; `pnpm -C editor exec tsx --test "app/**/*.test.ts"` +(**121 at `f3b187a` → 119 after commit 2**, unchanged after); `pnpm -C common exec tsx --test +controller/noCorpusWalkInRenderPaths.test.ts`. e2e once after commit 5, detached. + +--- + +## Commit 1 — `editor: the slice-5 leftovers — the nav timer names live routes, the pool lists its eight kinds, the aliases tab always has a site` + +**`editor/scripts/measure-nav.mjs`** + +- `:38` comment → + ``` + // Routes worth timing, with the segment name used to build a state tree. THE + // ELEVEN SINGLE-SEGMENT PAGES IN THE SIDEBAR (editor/app/lib/nav.ts), and + // only those: stateTree() below encodes ONE segment, so /sites/<id>/charts + // and /operations/<id> cannot be listed — each would need the nested + // [segment, {children: [param, …]}] form, a different tree per route. A + // RETIRED ROUTE ANSWERS 307 (editor/next.config.ts redirects); with + // redirect: "manual" that is a millisecond stub, not a page timing, and the + // >= 400 check below let five of them pass silently for a while. A 3xx is + // flagged now. + ``` +- `:39-50` → the eleven: `["/", "__PAGE__"]`, `/channels`, `/review`, `/operations`, + `/sites`, `/jobs`, `/workers`, `/cleanup`, `/saved-videos`, `/settings`, `/changelog` + (each `["/x", "x"]`). +- `:132` add `const moved = [doc, nav, refresh, pre].find((r) => r.status >= 300 && r.status < 400);` + and extend `:144`'s warning ternary: `bad` first, else `moved ? \` ⚠️ ${moved.status} + redirect — a stub, not a page timing\` : ""`. `APIS` (`:52-59`) unchanged (none redirected). + +**`editor/app/sites/page.tsx:29-41`** → +```ts +// Which live jobs the Pool section lists: the eight kinds the Pool's own +// buttons enqueue (BuildButtons.tsx → sites/lib/buildAction.ts), and only +// those. build-export and build-deploy are a site's Publish tab's, build-all +// and build-deploy-all are the batch panel's above — each has its own console +// and is not repeated here. +const BUILD_KINDS = new Set([ + "build-index", + "build-stats", + "normalize-transcripts", + "archive-transcripts", + "archive-combined-transcripts", + "normalize-live-chat", + "archive-live-chat", + "archive-combined-live-chat", +]); +``` +(Traced: `BuildButtons.tsx:12-20` imports exactly those eight actions; kinds at +`buildAction.ts:44,59,164,179,194,208,223,238`; `:88,123,260,319` are the four excluded.) + +**`editor/app/sites/components/EditorAliasesClient.tsx`**: `:3-5` → "Authoring UI for search +aliases. Two independent sections — the global dictionary and the site's overrides — each +edits a list of concept rows and saves the whole list via a server action." `:82-83` → +`siteConfig: AliasConfig; siteId: string;`. `:94-102` → the `<AliasSection … testid="site" />` +unwrapped (delete `{siteId && siteConfig && (` and `)}`). Only renderer +`sites/[siteId]/aliases/page.tsx:39-44` passes both non-null; `aliases.spec.ts` asserts +presence only — unchanged. + +--- + +## Commit 2 — `channels: the transcode stage is gone` + +**`git rm editor/app/channels/[slug]/components/stages/TranscodeStage.tsx`** (244 lines; its +labels "Transcode failures", "untranscoded list", "failed transcodings section" leave with it). + +**`editor/app/channels/[slug]/page.tsx`**: delete `:32` (`loadFailedTranscodings`), `:67` +(`TranscodeStage`), `:74` (`operationApplies,`), `:202`, `:227`, `:237`, `:289-290`, +`:378-387` (the `case "transcode"`), `:560-561`. `:253-261` → comment "`media`, `transcript`, +`digest`, `speakers` each spread their stages; there is no per-channel filter — every stage in +the Record applies to every channel." and `...OPERATION_GROUP_ORDER.flatMap((g) => +GROUP_STAGES[g]),`. `:267-271` → "…a saved link to a stage that no longer exists +(`?stage=transcode`, retired 2026-08-30) should still open the channel." `:482` → +`wrongFormatApplies={!!config!.audioFormat}` (`config!` — the block already spells +`config!.keepLatest` at `:488`). `:476-481` (the `buckets.untranscoded` fold into +`foreignAudioIds`) unchanged in this commit. `TRANSCRIPTION_QUEUE` (`:44`) stays (`:397`). + +**`…/lib/stageStatus.ts`**: `:9` delete `operationApplies,`; `:51` delete `| "transcode"`; +`:74-78` → +``` +// NOT 1:1 with the groups, and no honest derivation makes it so: `sync` owns +// NO stage (below), and a group may own more than one — `media` did while the +// transcode stage existed (retired 2026-08-30). So this is a Record of ARRAYS, +// spread in OPERATION_GROUP_ORDER — which yields exactly today's order and +// today's cardinality. It is a compile-time membership check, not a re-shaping +// of the page. +``` +`:85` → `media: ["download"],`; `:115-119` delete all five `"transcode…": "transcode"` map +entries (incl. `transcode-audio`); `:162`, `:190`, `:214`, `:220-221` delete; `:325-362` +delete the transcode block; `:630` delete `transcode,`. + +**`…/lib/channelFlow.ts`**: `:66` delete the union member; `:106-108` → "The pipelines that +run at this station, in dependency order. Empty for playlist, which is not an operation the +registry dispatches or counts — it keeps the plain coverage meter."; `:155-156`, `:230-231` +delete; `:318-334` (station), `:387` (spread), `:435-437` (siding), `:508` (gap), `:517` +(label) delete. + +**`…/components/flow/FlowStation.tsx:100-101`** → "The one station that is not a registry +operation — playlist — keeps the plain meter." +**`…/components/flow/AttentionStrip.tsx`**: `:37-38`, `:63-70` delete. +**`…/videos/page.tsx`**: `:7`, `:14`, `:134-135`, `:143-144` delete. +**`…/lib/videoRowsServer.ts`**: `:27-28`, `:49`, `:73`, `:76`, `:87`, `:105`, `:111` delete; +`:91` → `if (isFailedT) status = "failed";`. **Keep `:39`** (the set feeds `:113`). +**`…/lib/videoRows.ts`**: `:16-17`, `:30` delete; `:98-99` → `r.failedTranscription`; `:102` +→ `return r.failedTranscription;`. +**`…/components/VideoListPane.tsx`**: `:253` → "— no queued job."; `:279` → +`if (r.failedTranscription) next.add(r.id);`; `:726-731` delete `tcColor`; `:740` → +`row.failedTranscription ? "bg-destructive" : null`; `:756` delete the span. +**`…/bulkVideoActions.ts`**: `:11-14` delete; `:113`/`:138` "never triggers a transcode" → +"never queues a job"; `:202-204` → "Clearing failure markers is a set operation over the +failed-transcriptions file, so `succeeded` reports how many ids were actually removed (some +selected ids may not have been marked failed)."; `:215-218` delete; `:224` → +`succeeded: trxn.pruned,`. +**`…/whisperActions.ts`**: `:19-25` delete (keep `:26` `clearFailedTranscriptions`); +`:239-346` delete the four actions. +**`editor/app/jobs/jobReplayRegistry.ts`**: `:28`, `:30`, `:32-33`, `:216-235` delete. +**`editor/app/cleanup/lib/loadCleanup.ts`**: `:10` delete; `:26` → "// Housekeeping list size +(failed transcriptions)."; `:28`, `:90`, `:101` delete; `:137-143` → "// The brief already +carries the snapshot; only the failed-transcriptions list is still a per-channel read." + +`const failedT = await loadFailedTranscriptions(paths, channel.slug); return rowOf(channel, +channel.snapshot, failedT.length);`; `:218` → "also reads the failed-transcriptions list per +channel". +**`editor/app/cleanup/components/ChannelCleanupCard.tsx`**: `:14`, `:27`, `:39` delete; +`:45` → `const noHousekeeping = failedTranscriptions === 0;`; `:152-160` delete. +**`editor/app/cleanup/page.tsx:142`** delete. +**`…/components/stages/CleanupStage.tsx`**: `:28` → `// Both audio-format sweeps compare +against the channel's audioFormat; a channel with none has no "wrong" format and neither +section applies.` + `wrongFormatApplies: boolean;`; `:54` rename; `:136`, `:151` → +`{wrongFormatApplies && (`. +**`…/components/SocialChannelPanel.tsx:222`** → "no per-post download or transcription stage." +**`…/videos/[id]/components/VideoPanel.tsx`**: `:233`, `:535`, `:540`, `:555` delete (finding +13); the per-file rows untouched. +**`editor/app/components/channelWork/sections.test.ts:79`** → "(diarization, attribution-*)". +**`editor/app/operations/[id]/page.tsx:224-227`** → "diarization and both attribution +operations get no table at all". + +**Unit tests:** `channelFlow.test.ts` — `:46,48,52,58,64-65` and `:286-309` delete +(13 → 12). `stageOrder.test.ts` — `:6-9` the bare expression; `:17` trim; `:28-39` → +`function stageOrder(): StageId[]` with the bare spread; `:41-54` → title "the derived stage +order is the list that shipped", nine entries, no `"transcode"`; `:56-71` delete (3 → 2). +Editor total **121 → 119**. + +**Specs:** `whisper.spec.ts:227-260` delete (its helpers all still used 8–14× elsewhere in +the file). `bulk-actions.spec.ts`: `:128-129` delete; `:176` title → "Clear failed markers +prunes the failure file and clears the glyph"; `:180` → "// vidA failed transcription."; +`:182` delete; `:187` → "// One failure glyph is shown before clearing."; `:188` → +`toHaveCount(1)`; `:191` → `"1 selected"`; `:197` comment singular; `:203-205` delete +(`readFile` still used at `:200`). `helpers.ts:340` delete `| "transcode"` from +`ChannelStage`. + +--- + +## Commit 3 — `transcode: the operation is deleted — it never fired in production` + +**`git rm`** `common/controller/transcodeFailures.ts`, `transcodeUntranscoded.ts`, +`failedTranscodings.ts`, `removeFailedTranscodings.ts` (no other importer remains after +commit 2 — verified). + +**`common/lib/mediaFiles.ts`**: `:145-156` → one paragraph: +``` +// --------------------------------------------------------------------------- +// Which audio file to use when a video dir holds more than one: mp3 first, +// because that is what this corpus overwhelmingly holds (900 audio.mp3 against +// 12 audio.mp4 and 1 audio.aac) and what the transcribe and diarize paths were +// tuned against. A second ordering — the format most likely to be the ORIGINAL +// download, for picking a transcode SOURCE — left with the transcode operation +// on 2026-08-30; pickPreferredAudio keeps its `preference` parameter and every +// caller (transcribeOne, diarizeOne, operations.ts) takes the default. +``` +`:163-170` (`AUDIO_TRANSCODE_SOURCE_PREFERENCE`) delete. +**`common/lib/mediaFiles.test.ts`**: `:5`, `:179-187`, `:198-203` delete (19 → 17). +**`common/jobs/jobKinds.ts:348-371`** delete the four entries. +**`common/jobs/jobKinds.test.ts:97`** → +`assert.equal(jobKindLabel("clear-failed-transcriptions"), "clear-failed-transcriptions");`. +**`common/lib/operations.ts`**: `:77` delete the `ChannelConfig` import; `:1364-1384` delete +the entry; `:1422-1427` → "The auto-queue runner that dispatches this, when one does. +`dispatch` alone cannot answer it: download and transcription are `external` WITH a runner, +and an external operation with none is a legal entry (transcode was one, 2026-08-26 → 08-30). +A console that guessed from the id would hand such an entry the transcription runner's +controls — a live Start button over the wrong lane. Absent for every backfill kind: those are +dispatched by the sweep and the arbiter."; `:1433-1440` (`appliesTo` field + comment) delete; +`:1441-1443` → "// See Operation.settingsBlock. Absent for every external operation but sync: +nothing in settings.json configures a download as an operation."; `:1471-1478` +(`operationApplies`) delete. +**`common/lib/operations.test.ts`**: `:12`, `:1227`, `:1268-1290` delete (65 → 64); +`:1246-1251` reword per finding 8; `:1347-1348` → "// Sync is its own group, not `media`: +that group is download's /channels column group, and sync draws no column." +**`common/lib/pauseGates.ts`**: `:55-58` → +``` +// `runner` IS ASKED FIRST, and that order is load-bearing. An external +// operation may share a runner's queue and have no runner of its own (transcode +// did, on TRANSCRIPTION_QUEUE, until 2026-08-30); a queue-key map alone would +// hand it that runner's pause — a live Pause button over a lane that would +// never dispatch it. Such an entry's own answer is null. +``` +`:60` → "all seven ids". +**`common/lib/pauseGates.test.ts`**: `:87` → "pauseLaneFor answers for every catalog id"; +`:90-93` delete; `:104` → `7`. +**`common/controller/videoOperations.ts:16-17`** → "Download and transcription already have a +per-video surface". +**`editor/app/operations/[id]/page.tsx:149-152`**, **`OperationDetail.tsx:137-139`**, +**`operations/lanes.ts:64-70`** → reword per finding 8 (the agent's exact texts; `NoConsoleView` +stays). + +**Spec:** `operation-settings.spec.ts:238-239` delete (the `/operations/download` pair stays). +`/operations/transcode` now 404s through the catalog-as-route-table (`[id]/page.tsx:143-147`); +the `transcode` tag on `/workers` is gone with the entry. + +--- + +## Commit 4 — `snapshot: the untranscoded bucket is wrong-format audio` + +**`common/controller/channelSnapshot.ts`**: `:110` → +```ts + // Has audio on disk, none of it in the channel's audioFormat — the orphan + // half of what "Remove wrong-format audio" deletes (the other half is + // multipleAudioFormats: target present plus extras). Was `untranscoded` + // until 2026-08-30, when the transcode operation that read it as a to-do + // list was removed; the population itself is still the sweep's. Old + // snapshots keep a stray `untranscoded` key and read this one as [] until + // their next regen — no schema version, per-field optionality, as always. + wrongFormatAudio: string[]; +``` +`:765` accumulator, `:923` push, `:1145` write → renamed; `:873-875` comment → "// audio — +short-circuit so it doesn't land in wrongFormatAudio / downloadedNoTranscript (which would +transcribe corrupt audio, or offer the kept file to the wrong-format sweep) or the +wrong-format cleanup estimate (which would delete it)." The predicate (`:551-553`, `:918-922`) +unchanged. +**`…/lib/stageStatus.ts:25`** → `wrongFormatAudio: raw?.wrongFormatAudio ?? [],`. +**`…/page.tsx:478`** → `...buckets.wrongFormatAudio,`. +**`…/lib/videoRowsServer.ts`**: `:39` → `const wrongFormatAudio = new +Set(buckets.wrongFormatAudio);`; `:113` → `wrongFormatAudio.has(id) || +multipleAudioFormats.has(id),`. +No regen, no tooling (decided C). + +--- + +## Locator table — the specs + +| locator | before | after | spec | +|---|---|---|---| +| `channelStage("test-transcribe","transcode")`, `/^Transcode failures → mp3$/`, `"Transcode failures output"` | the stage | **gone** (test deleted; `ChannelStage` loses the member) | `whisper:227-260`, `helpers:340` | +| `getByTitle("failure recorded")` count | 2 | **1** | `bulk-actions:188` | +| `"2 selected"` | after Select failed | **`"1 selected"`** | `bulk-actions:191` | +| `readFile(failedTranscodingsFile)` | asserted pruned | **gone** | `bulk-actions:203-205` | +| `goto("/operations/transcode")` + no settings form | 200, no form | **gone** (404); `/operations/download` pair stays | `operation-settings:238-239` | +| `/^Transcode audio\.m4a → mp3$/`, `"Transcode audio.m4a to mp3 output"`, `"Transcode stage summary"` | per-file rows | **unchanged** | `video-page:118-133,398-440` | +| `getByLabel("pipeline status")` | three dots | unchanged (two dots) | `channel-embedded-video:25-55` | +| unknown `?stage=` → overview, 200 | pinned | unchanged; `?stage=transcode` is now a real instance | `channel-stage-selection:89` | +| `untranscoded: []` in seeded snapshot JSON | | **left** (stray key, ignored) | `auto-queue:80,133`, `channel-work:97`, `cleanup-actionable:30`, `disk-space:106`, `widget:42` | +| `"Select failed"`, `"Clear failed markers"`, wrong-format labels | | unchanged | `bulk-actions`, `ChannelCleanupCard` | + +## Verification + +1. After each of commits 1–4: the six tscs; `pnpm -C common test` (operations.test 65 → 64, + mediaFiles.test 19 → 17, pauseGates.test 6, jobKinds.test 4 — after commit 3); editor + units **121 → 119** (commit 2); the render-path guard test. +2. Grep gates after commit 4, over `editor/app common editor/scripts` (`*.ts *.tsx *.mjs`, + excluding `node_modules`): + - `transcod` (case-insensitive) → **only** the KEEP list: `common/controller/transcode.ts`; + `common/ytdlp/audioCheckedDownload.ts`; `common/ytdlp/downloadOneManaged.ts`; + `ffmpegStreamClassify.ts`/`ffmpegStreamProbe.ts`/`progressParsers.ts:180`/ + `scanCorruptMedia.ts:11,77`/`review/actions.ts:138`/`RunMediaScanButton.tsx:17` (probe + prose); `channelConfig.ts:36`; `mediaFiles.ts` (download-path prose + rewritten + `:145-152`); `mediaFiles.test.ts` (scratch-file prose); `pauseGates.ts` (rewritten); + `operations.ts` (rewritten); `OperationDetail.tsx` (rewritten); `operations/lanes.ts` + (rewritten); `VideoPanel.tsx` (per-file rows); `videoActions.ts`; + `videos/[id]/page.tsx:235`; `channelSnapshot.ts` (the new `:110` comment); + `stageStatus.ts:74-78`; `channels/[slug]/page.tsx:270`. Anything else is a miss. + - `untranscoded` → **0** under `editor/app common` (the five e2e fixtures remain, + deliberate). + - `failedTranscod|FailedTranscod|failed-transcodings` → **0** under `editor/app common` + and `editor/e2e` (the spec's const goes too). + - `operationApplies|appliesTo` → **0**. + - `TranscodeStage|transcodeApplies|transcodeDefaultQueueKey|AUDIO_TRANSCODE_SOURCE_PREFERENCE|runTranscodeFailures|runTranscodeUntranscoded|removeFailedTranscodings|clearFailedTranscodings|loadFailedTranscodings|pruneFailedTranscodings` → **0**. + - `"transcode"` (quoted id) → **0** under `editor/app common`. + - `wrongFormatAudio` → `channelSnapshot.ts` (4), `stageStatus.ts` (1), `page.tsx` (1), + `videoRowsServer.ts` (3); `wrongFormatApplies` → `CleanupStage.tsx` (4), `page.tsx` (1). + - `EXTERNAL_BAND_IDS = \["download", "transcription"\]` → exactly one hit + (`buildBands.ts:163`); `buildBands.test.ts` unchanged. + - `ids.length, 7` → `pauseGates.test.ts`; `length, 8` → 0 in `common/lib`. + - `BUILD_KINDS` set → 8 entries; `WRONG IN` → 0; `siteId: string \| null` → 0 in + `EditorAliasesClient.tsx`; `/actionable|/auto-queue|"/build"|"/charts"|"/aliases"` → 0 in + `measure-nav.mjs`. +3. e2e once after commit 5, detached (`cd editor && setsid nohup sh -c 'pnpm e2e -- <specs>; + echo exit=$?' > $CLAUDE_JOB_DIR/tmp/e2e.log 2>&1 < /dev/null & disown`), exactly: + `whisper.spec.ts bulk-actions.spec.ts operation-settings.spec.ts video-page.spec.ts + channel-stage-selection.spec.ts channel-line.spec.ts channel-embedded-video.spec.ts + cleanup-page.spec.ts cleanup-holds.spec.ts disk-space.spec.ts cleanup-actionable.spec.ts + channel-work.spec.ts auto-queue.spec.ts widget.spec.ts jobs.spec.ts workers.spec.ts + aliases.spec.ts build.spec.ts navigation.spec.ts perf-budget.spec.ts` (all names verified + to exist). Port fallback `PORT=3111 EXPORT_PORT=3110 OLLAMA_STUB_PORT=11535`; never kill + anything. +4. **No editor boot against `transcripts/`; nothing written under it.** The census is `jq` + only. +5. Manual (optional, `PORT=3021 pnpm dev:test`): `/channels/test-transcribe` has no Transcode + stage; `?stage=transcode` opens the overview; `?stage=cleanup` shows both audio-format + sections (fixture has `audioFormat`); a channel with none shows neither; + `/channels/test-transcribe/videos/vidA` shows Downloaded · Transcribed and the "Transcode + audio.m4a → mp3" row; `/operations/transcode` is 404; `/workers` offers no `transcode` + tag; `/cleanup` cards have no "Clear failed transcodings"; `node scripts/measure-nav.mjs + --base http://localhost:3021` lists eleven routes, none flagged. + +## Commit 5 — `plans: the transcode operation is removed, and the docs say so` + +- **`editor/CHANGELOG.md` `[Unreleased]` first bullet** (house voice): **The transcode + operation is gone — it never fired.** A channel page had a *Transcode* stage, + `/operations/transcode` had a "no console here" panel, `/cleanup` offered "Clear failed + transcodings", and the video list drew a third status dot — all for a re-encode step built + against two failures that never happened in production: in 68 channels, no snapshot has + ever listed a video as missing its target format, no `failed-transcodings` file has ever + held an id, and only four channels even met the stage's gate. Transcription never needed it + — a video whose audio is in another format transcribes from that file. What stayed is + everything that was never the operation's: the download path still re-encodes what it + extracts itself, the video page still offers **Transcode audio.\<ext\> → \<fmt\>** per + file, and both audio-format sweeps on the Cleanup stage and `/cleanup` are unchanged + (gated on the channel having an `audioFormat`, which is what they compare against). The + snapshot bucket behind the sweep is `wrongFormatAudio` now — its operator-facing name — + and old reports keep their stray key until their next refresh. A `?stage=transcode` + bookmark opens the channel overview. **Nothing on disk changes.** Also: the Pool's + running-jobs list names the eight kinds its buttons enqueue, and the site's Search aliases + tab no longer carries a "no site selected" branch that could not run. +- **`plans/editor-operations-ia.md:60`** → the Transcode row becomes "**REMOVED 2026-08-30 — + never fired in production; the band question is closed**" with the census one-liner, what + was deleted, what stayed, `EXTERNAL_BAND_IDS` is two permanently, plan + FACTS pointers + (the agent's exact row text). In "Slice 5, as shipped": `:767` (finding 14, `BUILD_KINDS`) + and `:776` (what stayed: `BUILD_KINDS`' mismatch, `EditorAliasesClient`'s null branch) each + gain "— fixed 2026-08-30, first commit of the transcode removal". +- **`plans/STATE.md`**: `:6` "Last updated" prepend (previous demoted to "Previously:"); + after `:326` item `14. ~~**The transcode operation**~~ — **REMOVED 2026-08-30**, `<sha1>` → + `<sha4>`. Plan: [`editor-transcode-removed.md`]; census in FACTS "Verified 2026-08-30 — + transcode: what it was, what stayed". The band question is closed by deletion; + `EXTERNAL_BAND_IDS` is two for good.`; `:328-331` → "**Recommended next (editor IA).** The + UI slices are done and the transcode band is no longer a candidate. What is left is + **Phase 6** and **slice 9**, which is gated on the sweep running in production."; + `:459-463` gains "**Removed 2026-08-30** — see item 14 above." +- **`plans/FACTS.md`**: append `## Verified 2026-08-30 — transcode: what it was, what + stayed`: the census table verbatim; the two building commits and which half of `bb380c6` + stayed; the `resolveAudioFile` fallback; the two `transcodeAudio` callers and + `persistencePlan.ts:92-94`; `transcode-audio` never registered; the bucket rename and why + no regen; the "runner first" order surviving without its example; catalog 8 → 7 and where + pinned; the worker tag being `/workers` not `/settings` (correcting `:2240`); the + `duplicates-page` merge hazard. At `:2229` prepend "**SUPERSEDED 2026-08-30 — removed, see + the section at the end of this file.**" +- Memory: left to the reviewer. + +## Out of scope + +`channelFlow.ts:153`'s unused `config` input. Regenerating snapshots or deleting the two +empty `failed-transcodings` files. Renaming the five e2e fixtures' `untranscoded: []`. Any +change to `EXTERNAL_BAND_IDS`, `buildBands`, or the pipelines rail. The download path +(`audioCheckedDownload`, `downloadOneManaged`, `ExtractionMode` docs). The per-file transcode +rows and `transcodeAudioAction`. The `duplicates-page` worktree. `measure-nav.mjs` measuring +dynamic routes. The `bulk-actions.spec.ts:133,169,210,243` prose comments. + +## Handoff — the cadence + +On approval, Fable does not implement (memory `plan-then-opus-implements`): write the plan to +`plans/editor-transcode-removed.md`, commit it alone, then spawn one `general-purpose` agent, +`model: "opus"`, with: the plan path; the shell caveats (quote every glob and every +`[slug]`/`[siteId]`/`[id]` path, `--include='*.ts'`, no bare `====` echo; POSIX loops inside +`sh -c`; `git commit -F <file under $CLAUDE_JOB_DIR/tmp>`; stage by explicit path, never +`git add -A`; `git rm` for the five deletions); never boot against `transcripts/`; e2e +detached (memory `e2e-run-detached`); the two trailer lines; the report contract (shas + one +line each; exact gate outputs incl. unit-test counts before/after each commit — expect +121 → 119 at commit 2, common 65/19 → 64/17 at commit 3; every grep gate's actual file list +against the expected one; e2e per spec with retries; every divergence and why; anything +undone). Fable reviews on return (`git log --oneline f3b187a..`; the `page.tsx` hunks — the +spread, the `:270` comment, `wrongFormatApplies`; `stageStatus.ts` — the `GROUP_STAGES` +paragraph, the map, the return; `channelFlow.ts`'s four Records; `videoRowsServer.ts:39,113`; +`bulkClearFailedMarkersAction`; the import lines; `CleanupStage` gating; the `VideoPanel` +strip; `operations.ts` — the entry, `appliesTo`, the rewritten comments, the `ChannelConfig` +import; `pauseGates.ts:55-60` + test; `mediaFiles.ts:145-152`; `channelSnapshot.ts:110` and +the four renames; the three specs and `helpers.ts`; `measure-nav.mjs`; `BUILD_KINDS`; +`EditorAliasesClient`; re-runs grep gates + `pnpm -C common test` + editor units, not e2e), +sends fixes via SendMessage, and reports.