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}%
+
Schedule as group
@@ -109,6 +142,15 @@
const [smartSel, setSmartSel] = React.useState("");
const items = existing ? EDIT_ITEMS : [];
+ // Weights are held per-item (as strings, so the number field edits smoothly); the % share is a
+ // display of the integer weights, and "Reset to fair share" sets every weight back to 1 (#404).
+ const [weights, setWeights] = React.useState(items.map((it) => String(it.weight ?? WEIGHT_MIN)));
+ const totalWeight = weights.reduce((s, w) => s + clampWeight(w), 0);
+ const pctFor = (i) => (totalWeight > 0 ? Math.round((clampWeight(weights[i]) / totalWeight) * 100) : 0);
+ const setWeightAt = (i, v) => setWeights((cur) => cur.map((w, j) => (j === i ? v : w)));
+ const alreadyFairShare = items.length > 0 && weights.every((w) => clampWeight(w) === WEIGHT_MIN);
+ const resetToFairShare = () => setWeights(items.map(() => String(WEIGHT_MIN)));
+
const collectionOptions = [
{ label: "Select a collection…", value: "" },
{ label: "Action Movies", value: "1" },
@@ -162,7 +204,28 @@
No collections added yet.
) : (
- 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.
+
+
+ Reset to fair share
+
+
+
)}