From 586b075cc1f75a632d34e02d6e7eea3091e1b014 Mon Sep 17 00:00:00 2001 From: Timothy Date: Sun, 19 Jul 2026 13:05:07 +0200 Subject: [PATCH] docs(404): mirror the weight UI into the MultiCollections design prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design-sync back-mirror of the #404 weight UI (PR #462) into the Claude Design prototype so repo + Claude Design stay byte-identical (docs/design-sync.md step 7). Pushed to the ChicoryTV Design System project via DesignSync in the same session. The prototype's EditItemRow now shows a per-source Weight input (1..1000) + computed % share, and the editor gains a "Reset to fair share" footer — a faithful mockup of the shipped screen. Prototype-only (design-system/), no shipped code. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../chicorytv-admin/MultiCollections.jsx | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/design-system/templates/chicorytv-admin/MultiCollections.jsx b/design-system/templates/chicorytv-admin/MultiCollections.jsx index 6bb0f0129..8320add89 100644 --- a/design-system/templates/chicorytv-admin/MultiCollections.jsx +++ b/design-system/templates/chicorytv-admin/MultiCollections.jsx @@ -18,12 +18,20 @@ ]; const EDIT_ITEMS = [ - { kind: "manual", name: "Friends", scheduleAsGroup: true }, - { kind: "manual", name: "Seinfeld", scheduleAsGroup: true }, - { kind: "manual", name: "The Office (US)", scheduleAsGroup: false }, - { kind: "smart", name: "Sitcoms — Unwatched, Short", scheduleAsGroup: false }, + { kind: "manual", name: "Friends", scheduleAsGroup: true, weight: 3 }, + { kind: "manual", name: "Seinfeld", scheduleAsGroup: true, weight: 2 }, + { kind: "manual", name: "The Office (US)", scheduleAsGroup: false, weight: 1 }, + { kind: "smart", name: "Sitcoms — Unwatched, Short", scheduleAsGroup: false, weight: 1 }, ]; + // Per-source weight bounds mirror the API's MultiCollectionItemWeight validator (1..1000, #404). + const WEIGHT_MIN = 1; + const WEIGHT_MAX = 1000; + const clampWeight = (raw) => { + const n = Math.trunc(Number(raw)); + return Number.isFinite(n) ? Math.min(WEIGHT_MAX, Math.max(WEIGHT_MIN, n)) : WEIGHT_MIN; + }; + const flushRow = (first) => ({ display: "flex", alignItems: "center", @@ -73,7 +81,7 @@ ); } - function EditItemRow({ item, first }) { + function EditItemRow({ item, first, weight, pct, onWeight }) { const [checked, setChecked] = React.useState(item.scheduleAsGroup); return (
@@ -92,6 +100,31 @@ {item.name} {item.kind === "smart" ? "Smart" : "Manual"} + {/* Per-source weight (#404): relative share of airtime under the Weighted Shuffle order. + Bounded 1..1000 (API validator); the % is a display of the integer share (3:1 → 75/25). */} +
+ Weight + onWeight(e.target.value)} + onBlur={(e) => onWeight(String(clampWeight(e.target.value)))} + style={{ + width: 62, + height: 30, + padding: "0 8px", + borderRadius: "var(--radius-sm)", + border: "1px solid var(--border-hairline)", + background: "var(--ctv-bg-sunken)", + color: "var(--text-primary)", + font: "var(--text-sm)/1 var(--font-sans)", + }} + /> + {pct}% +
) : ( - items.map((item, i) => ) + + {items.map((item, i) => ( + setWeightAt(i, v)} + /> + ))} + {/* fair-share footer (#404): fair-share is not a separate mode — it is Weighted Shuffle + with all weights left at 1, so this resets rather than writing a different order. */} +
+ + Weights set each source’s share of airtime under the Weighted Shuffle playback order (set on the schedule item). Equal weights = fair share. + + +
+
)} -- 2.47.3