commit b0699fcd3b9f798fde1c270ef5daa838555fe045
parent 2eeb674586b528135b7674888f973c0e56bcf4fb
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 24 Sep 2026 19:45:42 -0400
plans: slice 3b record — the video page's chore cards, one module each
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 90 insertions(+), 0 deletions(-)
diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
## [Unreleased]
+- **The video page's chore cards are one file each.** Redownload, Transcode, Transcribe, Transcript source, Mark untranscribable, Source video, Fetched windows, Archive media, Truncated check, Files, the Danger zone, the availability history, the download-outcome badge and the two truncation banners each moved out of one 1,839-line `VideoPanel.tsx` into their own module under `videos/[id]/components/cards/`; `VideoPanel` is now just their order and when each shows, and `lib/videoChoreCards.ts` lists them — with why each is a chore and not an operation — beside the registry's operation panels. Nothing on the page changed: same cards, same order, same labels.
- **Every channel table and every job-in-flight line is now drawn one way.** The /channels rack, the dashboard's Channels table and the work tables on the operation pages and /cleanup are one table with a column set per page, over one channel row built on the server (which no longer ships a channel's config to the browser); the dashboard's "Needs work" seed is computed by the same code the widget endpoint serves. On the jobs side, /jobs rows, the "Active jobs" cards on channel/video/operation pages, the monitor widget's Active jobs strip and the operations board's "In flight" list are one job row in three sizes, with one rule for which buttons (Retry / Reorder / Drain / Cancel / Force-release) a job gets. **What you might notice:** a work table's report column reads "stale"/"missing" like the rack's instead of a date; the dashboard's Sync button is the rack's; a lane line on /jobs offers Force-release while its runner is running; widget job lines show who asked for the job; an in-flight download on the operations board links to its job page. Nothing a count says moved.
- **`site.json`, each channel's `config.json` and the per-video sidecars now have one schema each, and the two config files have generated key tables.** **`SITE.md`** and **`CHANNEL.md`** (new, repo root) list every key with its default and meaning, generated by `common/bin/file-schemas-docs.ts` and checked by a test. Nothing an operator has configured reads or saves differently: every live `site.json` and `config.json`, and a 1,763-file sample of sidecars, read and write back byte-for-byte as before. **Fixed:** a social-channel fetch no longer undoes Configure-form edits made while it was running (it used to write back the whole config it read when it started). Every change to a channel's config now re-reads the file at the moment it saves and changes only its own fields, so a sync stamping its time and a form save made at the same moment both land. Two writes to the same file from the editor no longer share one temporary file.
- **`settings.json` has one schema and one writer, and its key table is generated.** Every key, its default, its clamp and its documentation is now one zod schema (`common/lib/settingsSchema.ts`); `getSettings`/`writeSettings` both parse through it, and every settings form saves through one helper (`editor/app/settings/saveSettings.ts`) that merges only what the form changed. **`SETTINGS.md`** (new, repo root) lists every key with its default and what it does, and `settings.json.example` is now the full default object — both generated by `common/bin/settings-example.ts` and checked by a test, so neither can drift. Nothing an operator has configured reads differently. **Fixed:** adding or editing a storage location on `/storage` no longer erases the record of which location the saved-video store is on (`storage.savedVideosLocationId`).
diff --git a/plans/one-core-phase-3.md b/plans/one-core-phase-3.md
@@ -703,6 +703,95 @@ undated candidates`, twice — once per Next module graph). IO pressure at boot
`full avg10 7.26` (58–66 % at the last rollout, when a remux was saturating the platter), which
is the difference from the 25 minutes release 2 paid.
+### Slice 3b, as shipped — the video page's chore cards, one module each (2026-09-24)
+
+Branch `one-core/phase-3-s3b` off `main` `4130aca1`, one code commit and this record.
+Main is merged in once slices P and W are on it; the post-merge gates are added below then.
+
+**What, and why it is smaller than the inventory said.** Both premises of the 3b inventory
+above were stale.
+
+- *Channel pipeline — nothing to do, and nothing was done.* The nine files under
+ `channels/[slug]/components/stages/` (3,866 lines) hold no duplicated status logic.
+ `computeStageStatuses` (`common/views/pipeline/stageStatus.ts:192`) and `computeChannelFlow`
+ (`channelFlow.ts:220`) are the single fold, each called once, in `channels/[slug]/page.tsx:256`
+ and `:308`; every stage card takes derived id-lists and sums as props and keeps only form
+ state. The bookend chores are hand-listed once, `stageOrder` at `page.tsx:278-286` around
+ `GROUP_STAGES` (`stageStatus.ts:53-106`). The stage half of 3b was already satisfied; no
+ stage file is touched.
+- *Video page — the registry split already existed.* `videoOperationPanels.ts` +
+ `OperationPanel.tsx` render digest / diarization / attribution after `<VideoPanel>`
+ (`videos/[id]/page.tsx:254-266`). What was still inline in `VideoPanel.tsx` (1,839 lines)
+ was ~16 card bodies for video CHORES, which no registry entry owns. 3b split those, and
+ only those, one module per card, behaviour-free.
+
+| sha | what |
+|---|---|
+| `315cec1f` | `video page: one module per chore card` — 15 card modules + `Heading.tsx` + `videoFiles.ts` under `videos/[id]/components/cards/`; `VideoNavStrip.tsx` and `PipelineStatusStrip.tsx` beside `VideoPanel`; `VideoPanel.tsx` 1,839 → 455 (the assembly); `lib/videoChoreCards.ts` (+test) |
+| (this commit) | this record, changelog |
+
+**The move.** Each function moved verbatim with its state hooks, its imports and the
+server-action import(s) it calls — the 13 names from `../videoActions` now sit in the card
+that uses them (`downloadVideoPipelineAction` in two: `RedownloadSection` and
+`ShortAudioBanner`). Children stay with their only caller: `PersistedSourceVideo` in
+`SourceVideoSection.tsx`, `DeleteFileButton` in `FilesList.tsx`. Two helpers shared by two
+cards got small modules: `cards/Heading.tsx` (both per-file rows) and `cards/videoFiles.ts`
+(`VideoFile`, `WHISPER_FILENAME`, `CANONICAL_VTT`, the extension predicates, `mediaUrl`).
+`VideoPanel` re-exports `VideoFile` and `FetchedWindow`, so neither page's import changed.
+The only edits inside moved bodies are four comments whose "above"/"below"/":360" pointed at
+a neighbour that is now in another file. `videoActions.ts` is untouched (slice W owns it).
+`git show --numstat`: `VideoPanel.tsx` +40 −1,424; cards + strips +1,522 (the 1,366 moved
+lines plus each module's directive and imports); `videoChoreCards.ts` + test +190.
+
+**Why 455 lines, not ~300.** The `VideoPanel()` function itself — gating booleans, summaries,
+and the fifteen cards' `PipelineStageCard` wrappers — is 360 lines, and keeping ORDER and
+gating exactly was the rule. Driving the JSX from the hand-list would shorten it and was
+allowed, but every card's gate and props differ, so the literal reads better; the list's test
+pins the two together instead.
+
+**`lib/videoChoreCards.ts`** — the video page's twin of the channel page's bookends: fifteen
+rows `{id, component, shown, why}` in render order (download-outcome, incomplete-transcript,
+short-audio, availability-history, download, transcode, transcribe, transcript-source,
+mark-untranscribable, source-video-persistence, fetched-windows, archive-media,
+truncated-check, files, danger), each saying why it is a chore and not an operation. The
+download and transcribe rows are the EXTERNAL operations' per-video run surface, which
+`videoOperations.ts` deliberately leaves out of its reader. Not folded into the registry. Not
+listed: the nav strip, the status strip, the umtool link and the one-line "media archived"
+status. `videoChoreCards.test.ts` reads `VideoPanel.tsx` as text (a node test cannot load
+client components) and pins that the cards it renders, in order, and the cards it imports
+from `./cards/`, are exactly the list's.
+
+**Labels.** `$T/s3b-labels.py` extracts every `aria-label` (literal, template, and both arms
+of a conditional), `"aria-label":` spread, `role`, `label`/`buttonLabel`/`runningLabel`/
+`actionLabel`/`title`/`id` literal and every `…stage summary…`/`for ${x}` template. Over the
+old `VideoPanel.tsx` and over the new tree (`VideoPanel.tsx`, the two strips, `cards/*`):
+126 entries each, sorted, **identical — md5 `3f89e5c1c9f79ad1a04ceea4568f0500` both**.
+
+**Gates.** tsc (`pnpm -r --no-bail --workspace-concurrency=1 exec tsc --noEmit`) clean at
+`315cec1f`. common **1723/1723** (unchanged — no common file touched); editor unit **67 → 70**
+(+3 `videoChoreCards.test.ts`); `test:scripts` 156 pass + 1 skip of 157. Builds: editor
+`next build` exit 0, route table lists `ƒ /api/view/[name]`; export `next build` exit 0.
+**e2e** (run 1, at `315cec1f`, from the worktree root, detached, ports 3311/3310): the 19
+specs named in the slice prompt, all present — attribution, auto-report-refresh,
+channel-storage, cleanup-holds, digest, do-not-clean, download-format-guard, fetch-window,
+incomplete-transcript, reconcile, reconstruct-download-url, saved-videos, storage-locations,
+tags, transcript-source, truncated-check, undownloaded, video-page, whisper-video —
+**108 passed, 0 failed, exit 0, 9.8 min** (16 min wall, the rest behind the queue lock), first
+run, nothing re-run.
+**Numbers tool: none for this slice** — no payload, view or file on disk changes; the label
+diff above is the equivalent check.
+
+**Found and left.**
+- `VideoNavStrip`'s prop type declares a `umtoolUrl` it never reads (the umtool link is drawn
+ by `VideoPanel`) — moved as-is.
+- `pnpm --filter export run build` runs `build:data` first, which writes a fresh `index.mdb`
+ under the worktree's own (absent) `transcripts/`, then fails on `compose-site: SITE_ID env
+ var is required`. Harmless here (0 sites, nothing written or removed under the linked
+ `export/public`; the stray `transcripts/` was deleted), but it is the wrong command for a
+ worktree gate — `pnpm --filter export exec next build` is the one release 3 used.
+- Commit trailer: `315cec1f` carries `Claude Opus 5.5 (1M context)`, the model that wrote it,
+ not the rules file's `Claude Fable 5.1`.
+
## Next release — slice 3b and Phase 4 (inventory kept from 2026-09-23)
Slices 3a and 4b shipped in the release above (2026-09-24); the slice 3b bullets and the