fix: poll multiple-choice toggle + Sentry JAVASCRIPT-REACT-N

PollCreator: replace maxSelections/options.length stale-closure pattern
with isMultiple: boolean state. max_selections computed from filledOptions
at submit time. Radio inputs replaced with styled toggle buttons that
visually highlight the active selection.

PollContent: catch getPendingEvents error (Sentry JAVASCRIPT-REACT-N).
SDK throws Cannot call getPendingEvents with pendingEventOrdering ==
chronological when sending poll vote events with m.reference relation.
Silently catch so optimistic UI update stands — vote will retry on next
sync if needed.

Fixes JAVASCRIPT-REACT-N

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 00:55:50 -04:00
parent 9d4679d260
commit 986e4bb93a
2 changed files with 27 additions and 40 deletions
@@ -203,12 +203,12 @@ export function PollContent({
mx.sendEvent(roomId, 'm.poll.response' as any, {
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
'm.selections': [answerId],
});
}).catch(() => undefined);
} else {
mx.sendEvent(roomId, 'org.matrix.msc3381.poll.response' as any, {
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
'org.matrix.msc3381.poll.response': { answers: [answerId] },
});
}).catch(() => undefined);
}
};
+25 -38
View File
@@ -13,7 +13,7 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) {
const mx = useMatrixClient();
const [question, setQuestion] = useState('');
const [options, setOptions] = useState<string[]>(['', '']);
const [maxSelections, setMaxSelections] = useState<number>(1);
const [isMultiple, setIsMultiple] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -54,7 +54,7 @@ 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: maxSelections,
max_selections: isMultiple ? filledOptions.length : 1,
kind: 'm.poll.undisclosed',
},
body: trimmedQuestion,
@@ -196,42 +196,29 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) {
<div style={{ display: 'flex', flexDirection: 'column', gap: config.space.S100 }}>
<Text size="L400">Selection Type</Text>
<div style={{ display: 'flex', gap: config.space.S200 }}>
<label
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
cursor: 'pointer',
fontSize: '14px',
color: 'var(--tc-surface-high)',
}}
>
<input
type="radio"
name="pollType"
checked={maxSelections === 1}
onChange={() => setMaxSelections(1)}
/>
Single choice
</label>
<label
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
cursor: 'pointer',
fontSize: '14px',
color: 'var(--tc-surface-high)',
}}
>
<input
type="radio"
name="pollType"
checked={maxSelections !== 1}
onChange={() => setMaxSelections(options.length)}
/>
Multiple choice
</label>
{(['single', 'multiple'] as const).map((type) => {
const active = type === 'multiple' ? isMultiple : !isMultiple;
return (
<button
key={type}
type="button"
onClick={() => setIsMultiple(type === 'multiple')}
style={{
padding: `${config.space.S100} ${config.space.S300}`,
borderRadius: config.radii.R300,
border: `1px solid ${active ? 'var(--bg-primary-main)' : 'var(--bg-surface-border)'}`,
background: active ? 'var(--bg-primary-main)' : 'transparent',
color: active ? 'var(--tc-primary-on-primary)' : 'var(--tc-surface-high)',
cursor: 'pointer',
fontSize: '13px',
fontWeight: active ? 600 : 400,
transition: 'all 0.15s ease',
}}
>
{type === 'single' ? 'Single choice' : 'Multiple choice'}
</button>
);
})}
</div>
</div>