From a4660a8163f9eaab5ff43ac980b69d8b4b60d461 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 11 Jul 2026 16:06:32 -0400 Subject: [PATCH] feat(polls): let creators set max selections for multiple-choice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The poll creator only offered single (max_selections 1) or multiple = pick ALL options — no way to run a "pick your top 2" poll, even though the display side already enforces an arbitrary max_selections ("Select up to N"). Add a "Voters can pick up to N of M options" control shown for multiple-choice polls. Defaults to the option count (preserving the old select-all behavior) until lowered; clamped to [2, filled option count] on submit. Co-Authored-By: Claude Opus 4.8 --- LOTUS_FEATURES.md | 1 + src/app/features/room/PollCreator.tsx | 34 ++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/LOTUS_FEATURES.md b/LOTUS_FEATURES.md index 3c059beb1..5efe081ae 100644 --- a/LOTUS_FEATURES.md +++ b/LOTUS_FEATURES.md @@ -866,6 +866,7 @@ player.kick). - `PollCreator.tsx` creates stable `m.poll.start` events (with a text fallback body for non-poll clients) - Supports 2 to 10 answer options; single-choice or multiple-choice +- **Max selections** — for a multiple-choice poll, a "Voters can pick up to N of M options" control sets `max_selections` (2 … option count), so you can run "pick your top 2" polls rather than only "select all". Defaults to the option count (unchanged "select all that apply" behavior) until you lower it; the display side already enforces the cap ("Select up to N") - **Results visibility toggle** — _Show live results_ (disclosed, default) vs _Hidden until ended_ (undisclosed) - Accessible via the `Icons.OrderList` button in the composer toolbar diff --git a/src/app/features/room/PollCreator.tsx b/src/app/features/room/PollCreator.tsx index ef2550c5c..21a3ae46c 100644 --- a/src/app/features/room/PollCreator.tsx +++ b/src/app/features/room/PollCreator.tsx @@ -34,6 +34,10 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) { const [question, setQuestion] = useState(''); const [options, setOptions] = useState(['', '']); const [isMultiple, setIsMultiple] = useState(false); + // For multiple-choice polls: the most options a voter may pick. Defaults high + // so an untouched multiple poll means "select all that apply" (the previous + // behavior); the effective value is clamped to the current option count. + const [maxSelections, setMaxSelections] = useState(10); // Results visibility: disclosed (live results, default) vs undisclosed (hidden // until the poll is ended). const [disclosed, setDisclosed] = useState(true); @@ -85,7 +89,9 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) { 'm.poll': { question: { 'm.text': trimmedQuestion }, answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })), - max_selections: isMultiple ? filledOptions.length : 1, + max_selections: isMultiple + ? Math.min(Math.max(2, maxSelections), filledOptions.length) + : 1, kind: disclosed ? 'm.poll.disclosed' : 'm.poll.undisclosed', }, body: fallbackBody, @@ -222,6 +228,32 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) { ); })} + {isMultiple && ( + + + Voters can pick up to + + ) => + setMaxSelections( + Math.min(options.length, Math.max(2, parseInt(e.target.value, 10) || 2)), + ) + } + style={{ width: '4rem' }} + aria-label="Maximum selections per voter" + /> + + of {options.length} options + + + )} {/* Results visibility */}