commit 5be0df44468f092bcbc21e351565e1066ea97fa5
parent e63a366dddf7d04189d49789aab5e72be0490c3a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 25 Sep 2026 00:39:14 -0400
plans: release 6 follow-ups slice — record and changelog
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 50 insertions(+), 0 deletions(-)
diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md
@@ -1,6 +1,9 @@
# Changelog
## [Unreleased]
+- **A video the server answers with HTTP 410 Gone is recorded as removed, not as an error.** Rumble answers a taken-down video with `HTTP Error 410: Gone`; the availability check read that as a generic error (one Rekieta Law Rumble video has said "error" since 2026-08-21), and a download that hit it could stop the batch. It now reads as removed, like "Video unavailable" does, so the check says so and a download skips that one video and carries on. Existing records change the next time the video is checked.
+- **umtool's report videos can fetch Rumble clips again.** The clip fetch and the source availability check in `umtool/report-to-video` ran yt-dlp without the browser fingerprint Rumble now requires, so every Rumble clip failed with 403 and every Rumble source looked missing. They now pass the same Rumble arguments as the editor, from the same single table.
+- **A transcript pulled back from a remote worker is written safely.** It used to be written straight onto `transcript.json`, so a crash part-way through left a truncated transcript; it now goes through the editor's one atomic write (temp file, then rename), like every other file the editor writes.
- **Rumble works again, and a Rumble full sweep that gets rate-limited no longer fails the sync.** Every Rumble request had started coming back 403 from Cloudflare unless yt-dlp presents a browser fingerprint (yt-dlp #17496), so Rumble downloads failed and a Rumble channel could not even be added. Every yt-dlp run for a Rumble channel — sync, download, metadata scan, availability check, the clip-window fetch and the new-channel probe — now passes `--impersonate chrome --sleep-requests 1`, from one table in the code; a channel's own extra yt-dlp arguments still come last and still win. Separately, a full sweep that hits HTTP 429 part-way through the listing used to fail the whole sync and try again on the next one, so a large channel (The Quartering on Rumble, 44 days) never synced at all. What it read is now treated as *incomplete* — not a listing, so nothing is flagged missing and the stored playlist is untouched: the job records the platform's rate-limit cooldown, says "sweep incomplete: 429 at page N of the listing, M entries" in its log, does the ordinary newest-first sync instead, and succeeds. Syncs for that platform are then refused until its cooldown ends, and the full sweep is tried again after that. Any other yt-dlp failure still fails the sync as before.
- **A site can turn off its visitors' per-video transcript downloads.** The transcript viewer on a published site has always offered three ways to take a video's text away: a **Download** menu (txt, srt, json), **Copy MD**, and **Copy download command** (a `yt-dlp` line for a marked clip). A site's settings form now has a checkbox for them, *Per-video transcript downloads*, beside the archive zips one. Unticked, the site's next build shows none of the three; **Share** and the clip marks stay. It is on by default, so a site nobody touches is unchanged, and the file stores `"transcriptDownloads": false` only when it is off (`SITE.md` has the key). The site's machine contract (`/corpus.json`, `llms.txt`, the manifests and shards the MCP server and report-to-video read) is published either way. The hub follows the same switch: the hub form on **Sites** has the same checkbox, stored as `"transcriptDownloads": false` in the hub's `homepage.json`, and it hides the three controls on the hub's Browse and Ask pages. The editor's own video pages are unaffected.
- **Channel rows no longer scroll over a group's controls on `/channels`.** Scrolled down and to the right, the pinned Slug column of every row painted over the pinned group header and its five station buttons (Sync, Download, Transcribe, Digest and the speaker lane), and took the clicks. The pinned Slug cell and the group header sat at the same stacking level, and the later rows won. The rack now has one named layer order, kept in one file: the Advanced panel, then the column header, then the group header, then the pinned checkbox and Slug cells. Nothing ties any more. The screenshot audit found four more problems, fixed as well. A group header's name and buttons now stay on screen however far the columns scroll across (they used to scroll off to the left). An Advanced panel opened near the bottom or the right edge scrolls itself into view instead of being cut off. The rule above a pinned group header moves with it instead of leaving a gap the rows showed through. On a phone, the column header no longer paints over the selection bar pinned to the bottom of the screen.
diff --git a/plans/release-6.md b/plans/release-6.md
@@ -0,0 +1,47 @@
+# Release 6 — follow-ups + Phase 4 slice 1
+
+`main` at `93dcb532` (release 5 merged: slice R, Rumble; slice X, visitor exports off). Release 5 is
+live. This release clears release 5's "found and left" items and Phase 3's deferred items, then lands Phase 4 slice 1.
+
+## Record
+
+### Follow-ups slice, as shipped — 410 is removed, umtool's Rumble args, one atomic write, a comment, measure-nav (2026-09-25)
+
+Branch `one-core/r6-followups` off `main` `93dcb532` (`main` had not moved). Five small items,
+one commit each. Four came from release 5's "found and left" list and Phase 3's deferred list. The
+fifth came from a live sidecar.
+
+| sha | what |
+|---|---|
+| `6d5cdbc3` | `common/lib/availability.ts` `parseUnavailableFromStderr`: `/http error 410/` joins the `deleted` group. There was no `404` pattern to copy, so it sits with "video unavailable" / "no longer available". The evidence is `rekietalaw-rumble` `v7e07us`'s sidecar (read only, not re-checked). It has read `error` since 2026-08-21, with `ERROR: [Rumble] v7e07us: Unable to download webpage: HTTP Error 410: Gone (caused by <HTTPError 410: Gone>)`. The classifier now returns `deleted`, so `classifyDownloadFailure` makes a download that hits it `per_video` (skip) where it used to be `unknown` (batch abort). `availability.test.ts` +1 test: that exact line, the per-video consequence, and 403/500 still `error` |
+| `9a88ee4b` | umtool's yt-dlp spawns carry the platform args. `report-to-video/*.mjs` run under bare `node`, and the runtime image is `node:20` (no type stripping), so they cannot import common's TS. **The shim moves the table rather than copying it.** `PLATFORM_ARGS` / `platformArgs` (+ a new `platformArgsForUrl`) now live in `common/ytdlp/platformArgs.mjs`, and `detectPlatform` in `common/lib/detectPlatform.mjs`. Both are plain JS with JSDoc types. `channelArgs.ts` and `platform.ts` re-export them, so no TS caller changed (the base tsconfig already has `allowJs`). common's `package.json` exports the two files by exact path; the `./lib/*` / `./ytdlp/*` patterns map to `.ts`. `umtool-report-to-video` gains a `yt-dlp-transcript-common` dependency. The lockfile gets that importer entry only: a plain `pnpm install` re-resolved ~280 lines of peer suffixes, so the entry was applied by hand and verified with `pnpm install --frozen-lockfile`. `build-video.mjs` exports `clipFetchArgs` (platform args before a retry's `extra`), and `check-availability.mjs` exports `simulateArgs`. New `platform-args.test.mjs` (3): Rumble gets the table's args in position, YouTube gets none |
+| `947f07aa` | `common/controller/transcribeOne.ts`: the remote upload path's `writeFile(transcriptPath, bytes)` is now `writeFileAtomic` (`lib/jsonFile-server.ts` — the brief's `writeJsonAtomic.ts` does not exist; slice W's idiom lives there). The shared-fs path is unchanged. No new test: the path needs a remote worker, and the repo does not module-mock. `transcribeOne.test.ts` (3) is green |
+| `71e2bb41` | `common/controller/channelSnapshot.ts` above `foldBucketLaneEntry`: the comment named the retired `editor/app/components/pipelines/buildBands.ts`. It now names `common/views/pipeline/buildBands.ts`. It also said the band's `blocked` "is this entry's missingInput"; it is `noTranscript` less the untranscribable and downloaded ones (`buildBands.ts:171-175`), so the sentence says that. Comment only |
+| `b4bd58a3` | `editor/scripts/measure-nav.mjs` (the brief said `plans/tools/`; it has always been under `editor/scripts/`). The six retired paths are `/api/pulse`, `/api/widget/{cleanable,actionable,sync}`, `/api/jobs/active` and `/api/workers`, which only reach a handler through `next.config.ts`'s rewrites. They are replaced by the `/api/view/<name>` paths the pages poll (`pulse`, `cleanable`, `widgetActionable`, `widgetSync`, `activeJobs`, `workers`), and the idle-pulse check uses `/api/view/pulse`. The 13 page routes were already current. Run once against the live :3001 (`--runs 1`, GETs only): no route or endpoint flagged, `/api/view/pulse` idle 8 ms |
+| *(this commit)* | this record, `[Unreleased]` bullets |
+
+**Gates** (worktree root). tsc (`pnpm -r --no-bail --workspace-concurrency=1 exec tsc --noEmit`)
+clean before every commit. common **1751/1751** (1750 + 1). test:scripts **159 pass + 1 skip**
+(156 + 1 skip + the 3 new). editor unit **72/72**. mcp **219/219**. `pnpm --filter editor exec
+next build` ok (50 s). `pnpm --filter export exec next build` ok (34 s). Before the builds, the
+worktree's `export/public/archives` link was dangling, because the primary has no `archives/` at
+the moment; it was removed, and no dangling links were left. EDITOR e2e `availability
+availability-backfill maybe-missing` (from `$T/f-specs.txt`; Playwright's substring match also
+picked up `pre-clean-availability`): **15 passed, 0 failed, 2.0 min**, with no wait in the
+queue. Numbers: **none**. No file format changed; `availability.json` keeps its shape, and a
+410 video's value changes only when it is next checked.
+
+**Found and left.**
+- **`v7e07us` still reads `error` on disk.** Nothing here re-checks it (the brief forbids live
+ checks). The next availability check or backfill that reaches it records `deleted`, and the
+ `history` gains that entry.
+- **umtool's `check-availability.mjs` has its own classifier** (`classify`, which already matches
+ a bare `410` as deleted). It differs from common's `parseUnavailableFromStderr`, and its states
+ are different ones (`maybe_missing`, `geo-blocked`, `restricted`). It was left alone because
+ it is umtool's editorial vocabulary, not a copy of the args table.
+- **`--sleep-requests 1` now applies to Rumble clip fetches too.** It comes with the table and
+ adds about a second per request on a clip fetch; that is harmless, and one table is the point.
+- **measure-nav's `nav`/`prefetch` columns read ~0.1 kb on every route of the live editor.** That
+ is the same for routes this commit did not touch. The state-tree encoding may have drifted with
+ Next 16.2; this commit only replaced the paths, so it was not investigated.
+- **Commit trailers** name `Claude Opus 5.5 (1M context)`, as in release 5.