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:
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.