Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 39fae2a994dfd234210f136af4964ebb75c914c8
parent 3784ea0529d7ff58772e35be746812e2a7bbc691
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 11 Sep 2026 11:55:27 -0400

editor: the focus banner, on the lane console it is a statement about

S4's banner half of plans/channel-priority.md. `FocusBanner.tsx` — a stub since
S0 — says the one thing a focus creates and nothing else on the page can: "why
is only jeralyzer moving?" A console can show a runner running, a ladder full of
rungs and thousands pending and still not say that three quarters of those rungs
are held behind a group at the top.

`Focus: <name> (N channels) · <focus> pending in this lane · <rest> waiting
behind it · M channels held`, from `focusSummary` over the lane's own compiled
`pendingByLeaf`. The numbers are PER LANE by construction, so the same focus
reads differently on the four consoles — which is the point: a focus can be
holding one lane and exhausted on another, and the exhausted case says so
instead of showing a held count of zero.

It renders nothing when no focus resolves, including a focus that resolved to no
channels (an unknown siteId compiles no focus group and holds no one), so a
corpus with no priorities set sees no new pixel.

WHERE: OperationDetail, between HowPriorityWorks and RunnerOperationView — above
the page's one `<section data-lane>`, never inside it. That section's contract
forbids a nested <section> and reserves role="status"; the banner is neither,
and being outside keeps it clear of every `section[data-lane]`-scoped lookup in
the suite.

NO END-FOCUS BUTTON HERE, deliberately. `settings.channelPriority` has exactly
one writer (`saveChannelPriorityAction`, S3) and it is not on this branch; a
second writer for one button is the thing the model was built to avoid. The
banner links to /channels instead, and carries an `endFocus` slot for a page
that already holds that writer to drop its own control into. S5 wires S3's
control through that slot rather than teaching this component to post.

`HowPriorityWorks` gains the paragraph that says where the rules come from, and
it answers in both directions: generated from the channel priorities, or
hand-authored here because none are set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/channels/components/FocusBanner.tsx | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Meditor/app/operations/components/HowPriorityWorks.tsx | 39++++++++++++++++++++++++++++++++++++++-
Meditor/app/operations/components/OperationDetail.tsx | 19++++++++++++++++++-
3 files changed, 134 insertions(+), 21 deletions(-)

diff --git a/editor/app/channels/components/FocusBanner.tsx b/editor/app/channels/components/FocusBanner.tsx @@ -1,25 +1,31 @@ -// THE FOCUS BANNER — a STUB, filled in by slice S4. +// THE FOCUS BANNER — what a focus is doing, said once, wherever dispatch is +// being watched. // -// 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. +// It answers the one question a focus creates and nothing else on the page can: +// "why is only jeralyzer moving?" A lane console can show a runner running, a +// ladder full of rungs and a pending count of thousands and still not say that +// three quarters of those rungs are being held behind a group at the top. // -// 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. +// NOT A `role="status"`, and not a <section>. It sits immediately above +// RunnerOperationView's `<section data-lane>`, whose structural contract +// reserves `role="status"` for "Saved." and forbids a nested <section> — and +// this is a persistent statement of state, not a live region announcing a +// change. `data-focus-banner` is how a test finds it. // -// 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>`. +// NOT AN AutoRunnerIdleReason either. A lane whose focus group holds the rest is +// not idle, it is dispatching focus work; "M channels held" is a DISPLAY fact, +// computed from counts the status panel already had — one pass over +// `pendingByLeaf` keyed by compiled leaf id, and no new read. // -// 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. +// NO WRITER OF ITS OWN. Ending a focus writes `settings.channelPriority`, and +// that document has exactly one writer (`saveChannelPriorityAction`, S3). This +// component therefore LINKS to /channels rather than posting an action of its +// own — a second writer for one button is the thing the model was built to +// avoid. `endFocus` is the slot a page that already holds that writer drops its +// own control into; the link is what every other placement gets. +import type { ReactNode } from "react"; +import Link from "next/link"; import type { AutoQueueKind } from "yt-dlp-transcript-common/lib/autoQueueTypes"; import type { FocusSummary } from "yt-dlp-transcript-common/lib/channelPriority"; @@ -32,12 +38,65 @@ export type FocusBannerProps = { // 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; + // An "End focus" control, supplied by a page that already owns the priority + // writer. Omitted everywhere else, where the link below is the way out. + endFocus?: ReactNode; }; +function channelCount(n: number): string { + return `${n} channel${n === 1 ? "" : "s"}`; +} + export default function FocusBanner({ summary, + name, + lane, + endFocus, }: FocusBannerProps): React.ReactNode { - // Nothing focused, nothing to say. S4 replaces the rest of this body. + // Nothing focused, nothing to say — including a focus that resolved to no + // channels at all, which compiles no focus group and holds no one. if (!summary.active) return null; - return null; + + const label = name ?? summary.siteId ?? "selected channels"; + + return ( + <div + data-focus-banner={lane ?? "all"} + data-focus-holding={summary.holding ? "true" : "false"} + className="flex flex-wrap items-center gap-x-3 gap-y-2 rounded-md border border-brand/30 bg-brand-soft px-3 py-2 text-sm" + > + <span className="font-medium text-foreground"> + Focus: {label} ({channelCount(summary.channelCount)}) + </span> + + {lane && ( + <span className="tabular-nums text-muted-foreground"> + · {summary.focusPending.toLocaleString()} pending in this lane ·{" "} + {summary.otherPending.toLocaleString()} waiting behind it + </span> + )} + + <span className="text-muted-foreground"> + {summary.holding ? ( + <>· {channelCount(summary.heldChannels)} held</> + ) : ( + // The focus has nothing left here, so strict descent has already + // fallen through to the groups below it. Worth saying: it is the + // moment the operator is waiting for, and the banner is the only + // thing that can see it. + <>· nothing left to focus here — the rest of the lane is running</> + )} + </span> + + <span className="ml-auto flex items-center gap-3"> + {endFocus} + <Link + href="/channels" + className="underline underline-offset-2 hover:text-brand" + > + Channel priorities + </Link> + </span> + </div> + ); } diff --git a/editor/app/operations/components/HowPriorityWorks.tsx b/editor/app/operations/components/HowPriorityWorks.tsx @@ -12,8 +12,14 @@ import { // It is reference material — true, worth having, and read once — so it was // costing every subsequent visit the height of the answer to "what is it doing // right now", which is the question people actually arrive with. +// +// `compiled` says whether the rules below are GENERATED from the channel +// priorities rather than hand-authored here. It changes what the last paragraph +// claims, because with a priority model set "read top to bottom" is still true +// and "edit them here" is not: the compiler wins at dispatch, so a rule typed +// into this tree could not change what the lane does. -export function HowPriorityWorks() { +export function HowPriorityWorks({ compiled = false }: { compiled?: boolean }) { const [open, setOpen] = useState(false); return ( <Collapsible open={open} onOpenChange={setOpen}> @@ -35,6 +41,37 @@ export function HowPriorityWorks() { claims. Rule order still wins — for a pure newest-first archive, use one catch-all rule. </p> + {/* THE TREE IS GENERATED, and this is where that is said in prose — + the read-only ladder says it as a state, this says it as a rule. + Drawn in both cases because "where do the rules come from" has an + answer either way, and the two answers are different. */} + <p> + {compiled ? ( + <> + <strong className="text-foreground"> + These rules are generated + </strong>{" "} + from the channel priorities — one tier per channel plus one + focus — set on{" "} + <Link href="/channels" className="underline"> + the channels page + </Link> + . All four lanes are compiled from that one document, so a + focus group sits at the top of every one of them and the tree + below is read-only here. + </> + ) : ( + <> + No channel priorities are set, so these rules are hand-authored + here. Setting a tier or a focus on{" "} + <Link href="/channels" className="underline"> + the channels page + </Link>{" "} + generates all four lanes&rsquo; rules instead, and this tree + becomes read-only. + </> + )} + </p> <p> A video is claimed by exactly one rule (the first that matches), so overlapping rules never double-process it. This is independent of the{" "} diff --git a/editor/app/operations/components/OperationDetail.tsx b/editor/app/operations/components/OperationDetail.tsx @@ -5,6 +5,7 @@ import type { AutoQueueKind } from "yt-dlp-transcript-common/jobs/autoQueueState import type { AutoQueueStatusPayload } from "../status"; // Type-only: syncRow.ts is a server module. See OperationRail. import type { SyncRowView } from "../syncRow"; +import FocusBanner from "../../channels/components/FocusBanner"; import { HowPriorityWorks } from "./HowPriorityWorks"; import { OperationRail } from "./OperationRail"; import { RunnerOperationView } from "./RunnerOperationView"; @@ -124,7 +125,23 @@ export function OperationDetail({ activeJobs={activeJobs} /> )} - <HowPriorityWorks /> + <HowPriorityWorks compiled={data[runnerKind].policyCompiled} /> + {/* THE FOCUS BANNER, OUTSIDE THE LANE SECTION. RunnerOperationView + opens the page's one `<section data-lane>` and its contract + forbids a nested <section> and reserves role="status"; the banner + is neither, and it sits above rather than inside so a + `section[data-lane]`-scoped lookup in the suite never sees it. + + Its numbers are THIS LANE's: `focusPending`/`otherPending` are + sums over the lane's own compiled `pendingByLeaf`, so the same + focus reads differently on the four consoles — which is the point, + since a focus can be holding one lane and exhausted on another. + It renders nothing at all when no focus resolves. */} + <FocusBanner + summary={data[runnerKind].focus} + name={data[runnerKind].focusName ?? undefined} + lane={runnerKind} + /> <RunnerOperationView kind={runnerKind} title={RUNNER_TITLE[runnerKind]}