feat(polls): complete the poll lifecycle — voting, undisclosed, end, spec-correct
Upgrade the MSC3381 poll feature from a leaky half-implementation to a complete, cross-client-correct one: - End/close a poll (creator or redact-PL mod) via inline confirm → m.poll.end; locks voting, reveals results, marks winner(s); only pre-end responses count. - Honor poll kind: undisclosed polls hide counts/percent/bars/total until ended (creator gets a Show-live-results vs Hidden-until-ended toggle; default live). Previously every poll was created undisclosed yet the UI leaked live results. - Enforce max_selections for multi-choice; radiogroup/checkbox a11y with arrow-key roving and an AT-announced winner. - Robust, dual-namespace wire handling: parse BOTH stable (m.poll/m.id/m.selections) and unstable (org.matrix.msc3381.poll.*) by hand — matrix-js-sdk 41.7.0's PollStart/Response parsers only understand the unstable bodies, so delegating to them broke every stable poll (caught in agent review). Use the SDK Poll model only for end validation + before-end filtering. - Pure tally/visibility/winner/parse logic extracted to utils/poll.ts with 14 tests incl. a stable/unstable wire-format round-trip. Reviewed by 3 agents (spec/cross-client, logic, a11y/UI); findings applied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1314,13 +1314,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
mEvent.getType() === 'm.poll.start' ||
|
||||
mEvent.getType() === 'org.matrix.msc3381.poll.start'
|
||||
)
|
||||
return (
|
||||
<PollContent
|
||||
content={mEvent.getContent()}
|
||||
roomId={room.roomId}
|
||||
eventId={mEvent.getId() ?? undefined}
|
||||
/>
|
||||
);
|
||||
return <PollContent mEvent={mEvent} room={room} canRedact={canRedact} />;
|
||||
if (mEvent.getType() === MessageEvent.RoomMessageEncrypted)
|
||||
return (
|
||||
<Text>
|
||||
@@ -1449,11 +1443,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
{mEvent.isRedacted() ? (
|
||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||
) : (
|
||||
<PollContent
|
||||
content={mEvent.getContent()}
|
||||
roomId={room.roomId}
|
||||
eventId={mEvent.getId() ?? undefined}
|
||||
/>
|
||||
<PollContent mEvent={mEvent} room={room} canRedact={canRedact} />
|
||||
)}
|
||||
</Message>
|
||||
);
|
||||
@@ -1506,11 +1496,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
{mEvent.isRedacted() ? (
|
||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||
) : (
|
||||
<PollContent
|
||||
content={mEvent.getContent()}
|
||||
roomId={room.roomId}
|
||||
eventId={mEvent.getId() ?? undefined}
|
||||
/>
|
||||
<PollContent mEvent={mEvent} room={room} canRedact={canRedact} />
|
||||
)}
|
||||
</Message>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user