commit 15300ae7349e3d6a313ced7d35c4a2b5f12aab75
parent b29e34ccb4e9dce6bbf6bc9375357833e7700e4d
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 23 Sep 2026 20:20:01 -0400
plans: phase 3 release 2026-09-23 (slices 2 + 4a) and the next release
A release head above the slice records (merge order, gates on a7501cb3,
two e2e placeholders), a next-release section for slices 3 and 4b with the
deferred follow-ups checked open, and the two slice records corrected
against the branches (6c11b094 and b5094303 were missing; line and test
counts updated to the tips).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
1 file changed, 108 insertions(+), 10 deletions(-)
diff --git a/plans/one-core-phase-3.md b/plans/one-core-phase-3.md
@@ -478,16 +478,109 @@ claimed fixed; the reads are in-memory).
> ONCE via `Promise.all` and hands the same pair to all four lanes. `getAutoRunnerStatus` is
> still called per lane and is genuinely in-memory.
+## Release 2026-09-23 — slices 2 and 4a
+
+`main` `54cf1b31` → **`ae2fa5a9`**. Two branches, both off `54cf1b31`, merged in this order:
+
+1. **`one-core/phase-3-s2` → `8d6e84f6`** — one polling route. Five commits, `5397f83c`
+ `074f09a2` `edf56370` `4288009b` `c205b270`.
+2. **`one-core/phase-3-s4a` → `ae2fa5a9`** — one zod schema for `settings.json`, one editor
+ writer, the example and the key table generated. Seven commits, `50215ab5` `e120a2a5`
+ `f1abe024` `85478527` `0316e988` `5b43dc8d` `81deae69`, then `main` merged in as
+ `7035316c`.
+
+**Why that order:** slice 2 first, so a rewrite sits in front of every polling request on a
+green `main` *before* the settings reader underneath those views changes. Had the order been
+reversed, a regression in the schema would have surfaced through eight route files that were
+about to be deleted, and slice 2's before/after numbers would have been measured across two
+changes at once.
+
+**Scope, decided with the operator 2026-09-23.** This release is slice 2 and slice 4a only.
+Slice 4a is `settings.json` alone — `site.json`, channel `config.json` and the sidecars are
+slice 4b. Slice 3 and slice 4b are the next release. zod is adopted (one dependency, in
+`common/`), which spends the veto `one-core.md` left open. Gate B from Phase 1 rode along as a
+prelude — a settings change through the existing surface plus a record, no code
+([`one-core-phase-1.md`](one-core-phase-1.md#gate-b--passed-2026-09-23)).
+
+**Gates on merged `main` `ae2fa5a9`:** `tsc --noEmit` clean across the workspace; common
+**1663** (1625 + 13 from slice 2 + 25 from slice 4a); editor unit **67** (59 + 4 + 4);
+`pnpm run test:scripts` **156 + 1 skip**; mcp **219**; editor and export `next build`
+green, with `ƒ /api/view/[name]` in the route table and no zod in any client chunk.
+
+Full editor suite on `ae2fa5a9`: <<E2E-FINAL>>
+Export suite: <<E2E-FINAL>>
+
+**Record corrections made while checking the two records below against the branches**
+(2026-09-23; where a record and the code disagreed, the code won): slice 2 is five commits,
+not four — the review fix `c205b270` came after the record was written, and it moved the
+`widgetActionable` row mapping out of `views.ts`, so the replacement is 176 lines + an 84-line
+test at the tip, not 185 + 66; editor unit is 63 at that tip, not 61. Slice 4a's table did not
+list its own record (`5b43dc8d`) or the review fix (`81deae69`), and the review fix added two
+settingsDocs wiring tests, so common on that tip is 1650, not 1648. Both records said
+"unmerged"; both are merged.
+
+## Next release — slice 3 and slice 4b
+
+Not started. The facts below were checked against `ae2fa5a9` on 2026-09-23.
+
+**Slice 3 — one drawing per noun.**
+
+- *Channel row.* Two `ChannelsTable`s — `editor/app/channels/components/ChannelsTable.tsx`
+ (966 lines, the rack) and `editor/app/components/dashboard/ChannelsTable.tsx` (192) — plus
+ `editor/app/components/channelWork/ChannelWorkTable.tsx` (178). One table with a column set.
+- *Job in flight.* `jobs/components/RunningJobsList.tsx` (158), `jobs/components/LaneStrip.tsx`
+ (88), `operations/components/InFlightList.tsx` (85) and the widget's own
+ `ActiveJobsStrip`/`JobRow` (`widget/components/MonitorWidget.tsx:989,1033`) onto the
+ `JobRowView` renderer with a `variant`. **`InFlightList` does not draw `JobRowView` today**:
+ it draws `AutoQueueKindStatus.runner.inFlight` (`InFlightList.tsx:16,20`), so it needs a
+ mapping to the row, not just a re-skin.
+- *Channel pipeline.* The status logic in the nine files under
+ `channels/[slug]/components/stages/` (eight `*Stage.tsx` cards plus `OperationWork.tsx`);
+ each stage keeps its body. `one-core.md` said eight.
+- *Video page.* `channels/[slug]/videos/[id]/components/VideoPanel.tsx` is **1,839 lines**
+ (`one-core.md` said 1,586), with download, transcode and transcription still inline;
+ `videoOperationPanels.ts` (`…/videos/[id]/lib/`) is the enumeration to split along.
+
+**Slice 4b — the rest of the schemas.** `site.json`; channel `config.json`, with its mutable
+sync state (`lastSyncedAt`, `lastFullSweepAt`, …) declared as such and the file NOT split;
+the sidecar `X.ts` / `X-server.ts` pairs → one `sidecar(name, schema)` each (read, atomic
+write, mtime freshness, the `transcript.<x>.<y>` naming guard). Slice 4a's pattern is the
+template: `settingsField(coerce)` over the existing sanitizer, no `.default()`, no
+`.passthrough()`, zod kept out of `"use client"` import graphs.
+
+**Deferred follow-ups, each checked still open on `ae2fa5a9`:**
+
+- The backfill lane form's hint text still says *"836 videos still have media on disk and
+ ~76,270 would need re-downloading"* (`operations/components/settings/LaneSettingsForm.tsx:101-102`);
+ the page measured 679 reachable / 74,046 no media at gate B. A stale measured figure in
+ copy — fix the copy or derive it, in a code slice.
+- `plans/tools/phase1-numbers.ts` sums `missingInput` straight from the snapshot
+ (`COUNT_FIELDS`, `:57-65`) and never filters `handling: "youtube"`, so its `missingInput`
+ is the whole bucket, not what a backfill pass could reach.
+- `usePolledPayload`'s hang recovery is not unit-tested: the editor unit suite has no React
+ harness, so `lib/usePolledPayload.test.ts` pins `pollTimeoutMs` only (one test).
+- The nested settings blocks (`DigestSettings`, `DiarizationSettings`, …) are each one
+ sanitizer-backed `settingsField`, not zod objects; their docs are the `*_FIELD_DOCS`
+ records. Converting them is optional work, not a bug.
+- `editor/scripts/measure-nav.mjs:64-69` still requests the eight OLD API paths. They answer
+ through the rewrites, so it measures rewrite + view; say so if its numbers are compared
+ with an older run.
+- Accepted, not owed: nothing prunes stale per-app entries from the `transcriptionApps`
+ shadow any more (slice 4a, behaviour changes).
+
## Slice 2, as shipped — one polling route (2026-09-23)
-Branch `one-core/phase-3-s2` off `54cf1b31`, four commits, not merged:
+Branch `one-core/phase-3-s2` off `54cf1b31`, five commits, merged to `main` as `8d6e84f6`
+(2026-09-23). *Checked against the branch 2026-09-23: the table, the line counts and the
+editor-unit count were corrected for the review-fix commit `c205b270`.*
| commit | what |
|---|---|
| `5397f83c` | `common/views/{cleanable,widgetActionable}.ts` + tests; the two widget routes call them; seven type importers repointed off `api/widget/*/route` |
| `074f09a2` | `common/views/names.ts` (`VIEW_NAMES`, `ViewName`, `VIEW_CONTRACT`) + test; `editor/app/api/view/{[name]/route.ts,views.ts,pulseView.ts,views.test.ts}`; eight route files deleted; eight `rewrites()`; `e2e/view-route.spec.ts` |
| `edf56370` | `usePolledPayload` `git mv` to `editor/app/lib/`; JobsTable, useOperationsStatus, SyncConsole folded onto it; every client poll URL is `/api/view/<name>` |
-| (this commit) | `plans/tools/phase3-view-numbers.ts`, two stale comments, this record |
+| `4288009b` | `plans/tools/phase3-view-numbers.ts`, two stale comments, this record |
+| `c205b270` | review fixes — deviation 7 below |
**One route, eight rewrites, no redirect.** `/api/view/[name]` is `force-dynamic`, checks
the name against `VIEW_NAMES` before any handler runs (unknown ⇒ 404), and dispatches into
@@ -504,7 +597,7 @@ Gates:
|---|---|
| `pnpm -r exec tsc --noEmit` | clean after every commit |
| common tests | **1638/1638** (was 1625; +13: cleanable 3, widgetActionable 6, names 4) |
-| editor unit (`tsx --test "app/**/*.test.ts"`) | **61/61** (was 59; +2: pulse textual guard, cleanable assignability) |
+| editor unit (`tsx --test "app/**/*.test.ts"`) | **61/61** at `4288009b` (was 59; +2: pulse textual guard, cleanable assignability); **63** after `c205b270` (+1 dispatcher ban, +1 `pollTimeoutMs`) |
| `pnpm run test:scripts` | 156 pass / 1 skip / 0 fail |
| `next build` (editor) | clean; `├ ƒ /api/view/[name]` (dynamic), `├ ƒ /api/widget/presets` kept |
| e2e subset, 20 specs (`pulse`, `auto-refresh`, `dashboard`, `dashboard-paths`, `widget`, `jobs`, `jobs-active-order`, `jobs-channel`, `workers`, `worker-remote`, `auto-queue`, `lane-runner`, `scheduler`, `ops-api`, `disk-space`, `perf-budget`, `backfill`, `channel-storage`, `channel-rename`, `view-route`) | **174 passed, 0 failed, exit 0**, 8.0 min, one run, nothing re-run |
@@ -517,8 +610,9 @@ main, route bodies copied into a temporary variant of the script, since the view
exist yet) vs after (the committed script, importing the views): **diff empty**, 4,574
bytes each.
-Line counts: the eight deleted routes were 190 lines; their replacement is 185 (route 41,
-`views.ts` 87, `pulseView.ts` 57) + a 66-line test. `common/views/` gains 144 non-test
+Line counts: the eight deleted routes were 190 lines; their replacement is 176 at the branch
+tip (route 41, `views.ts` 78, `pulseView.ts` 57) + an 84-line test — 185 + 66 at `074f09a2`,
+before `c205b270` moved the row mapping out of `views.ts` and added the dispatcher ban. `common/views/` gains 144 non-test
lines (`names` 55, `cleanable` 42, `widgetActionable` 47) and 129 test lines. `editor/app`
over commits 1–3, `--no-renames`: +421 / −345.
@@ -556,7 +650,7 @@ Deviations:
never-nulled `data` are unchanged.
6. **Two comments named deleted routes**: `PipelineBand.tsx:140` and the
`widget/lib/syncInputs.ts` header now say the old path is rewritten to `/api/view/…`.
-7. **Review fixes (one commit after `4288009b`)**: the hook timeout above; the
+7. **Review fixes (`c205b270`, one commit after `4288009b`)**: the hook timeout above; the
`CONSTRUCTORS` text ban now also covers `api/view/[name]/route.ts`, so a hoisted
constructor in the dispatcher fails a test; `view-route.spec.ts` also strips
`disk.freeBytes` from the activeJobs comparison (a live statfs, equal only while the
@@ -580,8 +674,10 @@ the 20-spec subset above.
## Slice 4a, as shipped — one settings schema (2026-09-23)
-Branch `one-core/phase-3-s4a` off `main` `54cf1b31`, unmerged. Commits (shas after the
-trailer rewrite; this record is `plans:` commit 6 on top):
+Branch `one-core/phase-3-s4a` off `main` `54cf1b31`, seven commits; `main` (slice 2) merged in
+as `7035316c`, then merged to `main` as `ae2fa5a9` (2026-09-23). Shas are after the trailer
+rewrite. *Checked against the branch 2026-09-23: the last two rows and the post-review common
+count were added.*
| sha | what |
|---|---|
@@ -590,6 +686,8 @@ trailer rewrite; this record is `plans:` commit 6 on top):
| `f1abe024` | `lib/settingsSchema.ts`: `siteSettingsSchema` (31 fields, `.describe()` on each), `SiteSettings = z.infer`, `defaults()` = `defaultSiteSettings()` = `parse({})`; `lib/settings.ts` reduced to I/O (1,783 → 250 lines) and `export *`s the schema module; `settingsSchema.test.ts` |
| `85478527` | `editor/app/settings/saveSettings.ts` + unit test; 19 call sites in 11 files converted to patches; `writeSettings` is imported by one editor file |
| `0316e988` | `common/bin/settings-example.ts` (+`--check`), `lib/settingsDocs.ts`, generated `settings.json.example` + `SETTINGS.md`, `settingsDocs.test.ts`; SETUP.md points at SETTINGS.md |
+| `5b43dc8d` | this record; changelog entry |
+| `81deae69` | review fixes — every nested key documented (`lib/fieldDocs.ts`, 24 `*_FIELD_DOCS` records), see below |
**What moved.** Every type, constant, clamp and block sanitizer that was in `lib/settings.ts`
is in `lib/settingsSchema.ts` and re-exported, so no importer changed. `getSettings` =
@@ -659,7 +757,7 @@ differently from the old one (no legacy `whisper-cli`/`firefox` keys) — by des
`parallelTranscriptions: 1`) parse to byte-identical settings through main and through the
schema (included in the numbers above). The entrypoint does not read the example.
-**Review fix (one commit after commit 6).** SETTINGS.md now documents every
+**Review fix (`81deae69`, one commit after the record).** SETTINGS.md now documents every
NESTED key, not only the 31 top-level ones: each block type carries a
`<TYPE>_FIELD_DOCS: FieldDocs<Type>` record beside it (`lib/fieldDocs.ts`; the mapped type
requires one entry per key, optional keys and every union member's keys included, so an
@@ -678,7 +776,7 @@ copied example pins every default, `held` included.
**Gates.** tsc (`pnpm -r --workspace-concurrency=1 exec tsc --noEmit`; the parallel `-r` form
was OOM-killed, exit 137) clean after every commit. common **1625 → 1648** (+20 schema, +3
-docs); `test:scripts` 156 pass + 1 skip of 157 (unchanged); mcp 219/219; editor unit **59 → 63**;
+docs) at `5b43dc8d`, **1650** after `81deae69` (+2 docs wiring tests); `test:scripts` 156 pass + 1 skip of 157 (unchanged); mcp 219/219; editor unit **59 → 63**;
`next build` editor and export clean.
**e2e** (from the worktree root, detached, ports 3311/3310): auto-queue, backfill, digest,
diarization, attribution, scheduler, cadence-ui, storage-locations, channel-storage, workers,