commit da102eab935821d0e19695a72c31bb1dfa83de36
parent e4429059d864fd7c1239574f666482e2f9a0ac3d
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sun, 13 Sep 2026 11:18:58 -0400
channels: the focus is a line, and it counts what it holds
The focus control was a bordered card sitting above the table with the same
weight as the table itself. It is a statement about the corpus, so it reads
as one: an eyebrow, the label, a rule beneath, controls at h-7.
It also states the held COUNT - "Focus: site Foo - 61 held". That number is
the corpus-wide half of what the 61 orange per-row notes were saying, which
is what earns each row the right to shrink to a chip.
aria-label="channel focus", "focus site", "Focus site", "End focus",
"focus error" and the "Focus: {label}" / "No focus" text are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/editor/app/channels/components/ChannelFocusBar.tsx b/editor/app/channels/components/ChannelFocusBar.tsx
@@ -51,12 +51,17 @@ export type FocusSite = { siteId: string; title: string };
export function ChannelFocusBar({
sites,
focusLabel,
+ heldCount = 0,
}: {
sites: FocusSite[];
// What is focused right now, already resolved by the page ("site Foo",
// "2 channels"), or null when nothing is. The full banner with the per-lane
// pending numbers is S4's FocusBanner; this bar is only the control.
focusLabel: string | null;
+ // How many rows the focus is holding. Stated HERE, once, because it is one
+ // corpus-wide fact — which is what lets each held row shrink to a chip
+ // instead of repeating the same sentence 61 times.
+ heldCount?: number;
}) {
const { pending, error, run } = useBarAction();
const [siteId, setSiteId] = useState(sites[0]?.siteId ?? "");
@@ -64,12 +69,21 @@ export function ChannelFocusBar({
if (sites.length === 0 && !focusLabel) return null;
return (
+ // A STATEMENT, not a panel: one line under the header with a rule beneath
+ // it, not a card competing with the rack for weight. The eyebrow names it
+ // the way the nav groups and the deck's verb groups are named.
<div
aria-label="channel focus"
- className="flex flex-wrap items-center gap-2 rounded border border-border bg-card px-3 py-2 text-sm"
+ className="flex flex-wrap items-center gap-3 border-b border-border px-1 py-1.5 text-sm"
>
+ <span className="font-mono text-[10px] uppercase tracking-[0.16em] text-muted-foreground">
+ Focus
+ </span>
<span className="text-muted-foreground">
{focusLabel ? `Focus: ${focusLabel}` : "No focus"}
+ {focusLabel && heldCount > 0 && (
+ <span className="tabular-nums"> · {heldCount} held</span>
+ )}
</span>
{sites.length > 0 && (
<>
@@ -78,7 +92,7 @@ export function ChannelFocusBar({
value={siteId}
disabled={pending}
onChange={(e) => setSiteId(e.target.value)}
- className="rounded-md border border-border bg-card px-2 py-1 text-xs disabled:opacity-50"
+ className="h-7 rounded-md border border-border bg-card px-2 text-xs disabled:opacity-50"
>
{sites.map((s) => (
<option key={s.siteId} value={s.siteId}>
@@ -90,7 +104,7 @@ export function ChannelFocusBar({
type="button"
disabled={pending || !siteId}
onClick={() => run(() => focusSiteAction(siteId))}
- className="rounded-md border border-border px-2 py-1 text-xs hover:bg-muted disabled:opacity-50"
+ className="h-7 rounded-md border border-border px-2 text-xs hover:bg-muted disabled:opacity-50"
>
Focus site
</button>
@@ -101,7 +115,7 @@ export function ChannelFocusBar({
type="button"
disabled={pending}
onClick={() => run(() => endFocusAction())}
- className="rounded-md border border-border px-2 py-1 text-xs hover:bg-muted disabled:opacity-50"
+ className="h-7 rounded-md border border-border px-2 text-xs hover:bg-muted disabled:opacity-50"
>
End focus
</button>
diff --git a/editor/app/channels/components/ChannelsTable.tsx b/editor/app/channels/components/ChannelsTable.tsx
@@ -314,13 +314,23 @@ export function ChannelsTable({
return () => observer.disconnect();
}, []);
+ // HOW MANY ROWS THE FOCUS IS HOLDING, for the focus line's one-line summary.
+ // The reason itself stays per-row (each held chip carries the full sentence
+ // as its screen-reader text); the COUNT is the corpus-wide fact, and stating
+ // it once above the table is what lets the rows stop repeating the sentence.
+ const heldCount = channels.filter((c) => c.priority.heldReason).length;
+
return (
// On md+ this is a pane of the page's viewport-height flex column: the
// instrument bar and the deck are fixed chrome and only the region between
// them scrolls. Below md it has no height of its own and the document
// scrolls, exactly as it always has.
<div className="flex min-h-0 flex-1 flex-col">
- <ChannelFocusBar sites={sites} focusLabel={focusLabel} />
+ <ChannelFocusBar
+ sites={sites}
+ focusLabel={focusLabel}
+ heldCount={heldCount}
+ />
{/* THE INSTRUMENT BAR: what the rack is showing (grouping) on the left,
how to read it (the band legend, and the two-route explainer behind a
disclosure) on the right. Both used to live in an 11px stack UNDER 67