commit 0186d2f4ab15cab7f3228b1dbc2292d0bb7e332d
parent 92741a1277212425cbb3b72de61fc753498588dc
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 11 Sep 2026 11:24:38 -0400
editor: the two channel-priority components S3 and S4 will fill in
Empty-but-exported, with the props as the contract. They exist now so the two UI
slices each edit a file the other does not and can run fully in parallel on
separate branches: S3 fills in ChannelTierSelect, S4 fills in FocusBanner.
FocusBanner already honours the one rule S4 is written around — render nothing
when nothing is focused — so it is green on /channels before S3 exists and on the
lane consoles before anything writes a focus.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 84 insertions(+), 0 deletions(-)
diff --git a/editor/app/channels/components/ChannelTierSelect.tsx b/editor/app/channels/components/ChannelTierSelect.tsx
@@ -0,0 +1,41 @@
+// THE /channels TIER CONTROL — a STUB, filled in by slice S3.
+//
+// It exists at S0 only so S3 and S4 each edit a file the other does not: S3
+// fills this in and S4 fills in FocusBanner.tsx, and the two branches run in
+// parallel with no shared file. The props are the contract both slices code
+// against; the body is `null` until S3.
+//
+// What S3 makes of it: the cell that `ChannelSyncToggle` occupies today
+// (ChannelsTable.tsx) becomes a `<select>` over STORED_CHANNEL_TIERS with
+// `aria-label={`tier for ${slug}`}`, posting `setChannelTierAction(slug, tier)`
+// — one writer, `saveChannelPriorityAction`, recompiles the four roots in the
+// same saveSettings call. `focused` and `heldReason` are DISPLAY facts derived
+// from the same `focusSummary` the banner uses, not a second read. The
+// per-operation overrides sit behind a disclosure beside the select, with a
+// "Sync only" preset; `overrides` carries what is currently pinned.
+
+import type {
+ PriorityOperation,
+ StoredChannelTier,
+} from "yt-dlp-transcript-common/lib/channelPriority";
+
+export type ChannelTierSelectProps = {
+ slug: string;
+ // The channel's BASE tier (tierOf), not its effective tier for any one
+ // operation — the overrides are shown beside it, not folded into it.
+ tier: StoredChannelTier;
+ // Per-operation pins, normalised (only operations that differ from `tier`).
+ overrides?: Partial<Record<PriorityOperation, StoredChannelTier>>;
+ // This channel is in the active focus set. Focus is a corpus-wide selector,
+ // never a stored tier, so it is passed in rather than read off `tier`.
+ focused?: boolean;
+ // "Held — focus: <name>" for a non-focus row while a focus holds the lanes.
+ heldReason?: string | null;
+ disabled?: boolean;
+};
+
+export default function ChannelTierSelect(
+ _props: ChannelTierSelectProps,
+): React.ReactNode {
+ return null;
+}
diff --git a/editor/app/channels/components/FocusBanner.tsx b/editor/app/channels/components/FocusBanner.tsx
@@ -0,0 +1,43 @@
+// THE FOCUS BANNER — a STUB, filled in by slice S4.
+//
+// It exists at S0 for the same reason ChannelTierSelect.tsx does: S3 and S4
+// then each edit one file the other does not. It already honours the one rule
+// S4 is written around — RENDER NOTHING WHEN NOTHING IS FOCUSED — so it is
+// green on /channels before S3 exists and green on the lane consoles before
+// anything writes a focus.
+//
+// What S4 makes of it: `Focus: <name> (N channels) · <units> pending in this
+// lane · M channels held`, plus an "End focus" button posting `endFocusAction()`.
+// The numbers come from `computeLeafPending`, which the status panel already
+// computes, so the banner is one sum over `counts` and no new read.
+//
+// WHERE IT GOES on the lane consoles: OperationDetail.tsx, between
+// HowPriorityWorks and RunnerOperationView — i.e. OUTSIDE the `<section
+// data-lane>` that RunnerOperationView opens, whose contract comment reserves
+// `role="status"` and forbids a nested `<section>`.
+//
+// It is NOT a new AutoRunnerIdleReason. A lane whose focus group holds the rest
+// is not idle, it is dispatching focus work; "M channels held" is a display
+// fact.
+
+import type { AutoQueueKind } from "yt-dlp-transcript-common/lib/autoQueueTypes";
+import type { FocusSummary } from "yt-dlp-transcript-common/lib/channelPriority";
+
+export type FocusBannerProps = {
+ summary: FocusSummary;
+ // Display name for the focus: a site's title for a site focus, or a short
+ // channel list. Resolved by the caller — this file does no I/O and the model
+ // stores a siteId, not a title.
+ name?: string;
+ // The lane this banner is drawn beside, when it is on a lane console. Null on
+ // /channels, where the per-lane line is repeated for each ENABLED lane.
+ lane?: AutoQueueKind | null;
+};
+
+export default function FocusBanner({
+ summary,
+}: FocusBannerProps): React.ReactNode {
+ // Nothing focused, nothing to say. S4 replaces the rest of this body.
+ if (!summary.active) return null;
+ return null;
+}