Skip to content

feat(polls): implement MSC3381 polls#589

Open
Just-Insane wants to merge 13 commits into
SableClient:devfrom
Just-Insane:feat/polls
Open

feat(polls): implement MSC3381 polls#589
Just-Insane wants to merge 13 commits into
SableClient:devfrom
Just-Insane:feat/polls

Conversation

@Just-Insane
Copy link
Copy Markdown
Contributor

@Just-Insane Just-Insane commented Mar 29, 2026

Description

> ⚠️ Merge together with SableClient/docs#12

Implements Matrix polls (MSC3381) with a creator dialog and timeline renderer.

  • New PollCreatorDialog — question, 2–20 answers, disclosed/undisclosed type, voter visibility toggle (show/hide voter names), poll duration (presets: 1h / 12h / 24h / 48h / 1 week / custom date-time)
  • New PollEvent timeline renderer — vote buttons, live/final results bars, expandable voter lists per answer, expiry countdown in footer, auto-expire enforcement, end-poll action
  • Poll state assembled inline in PollEvent from org.matrix.msc3381.poll.start, .poll.response, and .poll.end events via paginated room timeline; respects show_voter_names and closes_at fields. Also handles stable m.poll.* types.
  • /poll slash command in RoomInput, gated by features.polls in config.json (defaults false)
  • Supports both stable (m.poll.*) and unstable (org.matrix.msc3381.poll.*) event types
  • Handles encrypted poll responses via MatrixEventEvent.Decrypted listener
  • Multi-select polls render with checkboxes; single-select with radio buttons
  • Keyboard accessibility via FocusOutline focus-visible styles on poll options

Spec: MSC3381
Related upstream issue: cinnyapp/cinny#563
Documentation: SableClient/docs#12

Fixes #

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

AI disclosure:

  • Partially AI assisted (clarify which code was AI assisted and briefly explain what it does).
  • Fully AI generated (explain what all the generated code does in moderate detail).

Poll state is assembled inline in PollEvent by paginating the room timeline to collect poll.response events keyed by sender, deduplicating so only the last vote per user counts, and tallying totals and percentages. PollEvent renders the resulting state as vote buttons with fill-bars; it re-fetches on RoomEvent.Timeline and MatrixEventEvent.Decrypted so it stays live even for encrypted responses. PollCreatorDialog builds the m.poll.start content with crypto.randomUUID() answer IDs and sends via mx.sendEvent. The /poll slash command is conditionally available based on clientConfig.features?.polls. The show_voter_names flag is a client-side display hint stored in the poll start event; the closes_at field is a Unix ms timestamp enforced client-side (voting blocked after expiry, UI shows countdown).

@Just-Insane Just-Insane requested review from 7w1 and hazre as code owners March 29, 2026 21:41
@Just-Insane Just-Insane marked this pull request as draft March 29, 2026 21:46
@Just-Insane Just-Insane force-pushed the feat/polls branch 3 times, most recently from 55449fb to 2ea98d6 Compare March 30, 2026 12:54
@Just-Insane Just-Insane marked this pull request as ready for review March 30, 2026 13:46
@dozro
Copy link
Copy Markdown
Member

dozro commented Mar 30, 2026

is it using the ui you showed in matrix chat or the ui of cinnyapp/cinny#2763 ?

@Just-Insane
Copy link
Copy Markdown
Contributor Author

is it using the ui you showed in matrix chat or the ui of cinnyapp/cinny#2763 ?

The UI from Cinny

@Just-Insane Just-Insane marked this pull request as draft March 31, 2026 20:13
@Just-Insane Just-Insane marked this pull request as ready for review April 8, 2026 21:54
Copilot AI review requested due to automatic review settings April 12, 2026 21:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Matrix MSC3381 poll support in the room timeline, including poll creation UI, vote/end handling, and a feature-flag gate via client config.

Changes:

  • Add poll event types/SDK exports and a features.polls client-config flag (default false).
  • Add PollCreatorDialog (poll creation) and PollEvent (timeline renderer + tallying/end action), plus supporting styles and tests.
  • Integrate poll rendering/filtering into timeline processing and add a /poll command pathway in RoomInput.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/types/matrix/room.ts Adds MSC3381 (unstable) poll event types to the room message type enum.
src/types/matrix-sdk.ts Re-exports poll constants/types from matrix-js-sdk for app usage.
src/app/hooks/useCommands.ts Adds /poll command entry for command discovery/autocomplete.
src/app/hooks/useClientConfig.ts Extends client config typing with features.polls.
src/app/hooks/timeline/useTimelineEventRenderer.tsx Adds timeline rendering for poll start events and suppresses rendering for response/end events.
src/app/hooks/timeline/useProcessedTimeline.ts Filters poll response/end events so they don’t appear as timeline items.
src/app/features/room/RoomInput.tsx Adds feature-gated poll creator dialog launch (via /poll) and sends poll start events.
src/app/features/room/poll/PollEvent.tsx Implements poll content extraction, tally computation, voting, ending, and results UI.
src/app/features/room/poll/pollEvent.test.ts Adds unit tests for poll parsing, tallying, and expiry formatting.
src/app/features/room/poll/PollEvent.css.ts Adds styles for poll option controls and layout.
src/app/features/room/poll/PollCreatorDialog.tsx Implements poll creation dialog (question/options/visibility/expiry/max selections).
src/app/features/room/poll/PollCreatorDialog.css.ts Adds styles for the poll creation dialog.
src/app/features/room/poll/index.ts Exports poll feature components/types.
config.json Adds features.polls: false feature flag to config.
.changeset/feat-polls.md Adds changeset entry for the new polls feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app/hooks/timeline/useTimelineEventRenderer.tsx Outdated
Comment thread src/app/hooks/timeline/useProcessedTimeline.ts Outdated
Comment thread src/app/features/room/poll/PollEvent.tsx
Comment thread src/app/features/room/poll/PollCreatorDialog.tsx Outdated
Comment thread src/app/features/room/poll/PollEvent.tsx
Comment thread src/app/features/room/poll/PollEvent.css.ts Outdated
Comment thread src/app/features/room/RoomInput.tsx Outdated
Comment thread src/app/features/room/poll/PollEvent.tsx
Comment thread src/types/matrix/room.ts Outdated
Just-Insane added a commit to Just-Insane/Sable that referenced this pull request Apr 13, 2026
- Add stable m.poll.* type aliases alongside unstable MSC3381 types
- Register stable poll types in useTimelineEventRenderer
- Fix datetime-local timezone bug in PollCreatorDialog (UTC→local)
- Add FocusOutline from folds for keyboard a11y on poll options
- Add MatrixEventEvent.Decrypted listener for encrypted poll responses
- Support multi-select polls with Checkbox component
Just-Insane added a commit to Just-Insane/Sable that referenced this pull request Apr 13, 2026
- Add stable m.poll.* type aliases alongside unstable MSC3381 types
- Register stable poll types in useTimelineEventRenderer
- Fix datetime-local timezone bug in PollCreatorDialog (UTC→local)
- Add FocusOutline from folds for keyboard a11y on poll options
- Add MatrixEventEvent.Decrypted listener for encrypted poll responses
- Support multi-select polls with Checkbox component
@Just-Insane Just-Insane marked this pull request as draft April 17, 2026 11:55
@dozro dozro added abandoned this pr seems abandoned question Further information is requested labels Apr 27, 2026
@dozro
Copy link
Copy Markdown
Member

dozro commented Apr 27, 2026

the amount of commits and line changes this pr wants to merge seems a bit off for the problem stated 🤔

Just-Insane added a commit to Just-Insane/Sable that referenced this pull request May 11, 2026
- Add stable m.poll.* type aliases alongside unstable MSC3381 types
- Register stable poll types in useTimelineEventRenderer
- Fix datetime-local timezone bug in PollCreatorDialog (UTC→local)
- Add FocusOutline from folds for keyboard a11y on poll options
- Add MatrixEventEvent.Decrypted listener for encrypted poll responses
- Support multi-select polls with Checkbox component
Just-Insane added a commit to Just-Insane/Sable that referenced this pull request May 11, 2026
- Add stable m.poll.* type aliases alongside unstable MSC3381 types
- Register stable poll types in useTimelineEventRenderer
- Fix datetime-local timezone bug in PollCreatorDialog (UTC→local)
- Add FocusOutline from folds for keyboard a11y on poll options
- Add MatrixEventEvent.Decrypted listener for encrypted poll responses
- Support multi-select polls with Checkbox component
Just-Insane added a commit to Just-Insane/Sable that referenced this pull request May 11, 2026
- Add stable m.poll.* type aliases alongside unstable MSC3381 types
- Register stable poll types in useTimelineEventRenderer
- Fix datetime-local timezone bug in PollCreatorDialog (UTC→local)
- Add FocusOutline from folds for keyboard a11y on poll options
- Add MatrixEventEvent.Decrypted listener for encrypted poll responses
- Support multi-select polls with Checkbox component
- Attachment card wrapper with header row (poll type label, ended state, vote count)
- RadioButton per answer, ProgressBar for results (disclosed/ended undisclosed)
- Reactive ended state via Poll.isEnded + PollModelEvent.End listener
- End poll confirmation modal for undisclosed poll creators
- messageLayout prop wired through to Attachment outlined for bubble layout
The polls toolbar button is removed from RoomInput. The /poll slash
command now opens PollCreator directly, keeping the feature accessible
via keyboard-driven workflow without cluttering the toolbar.
The poll response and end-poll sendEvent calls were missing their event
content bodies and .catch() handlers, so voting and ending polls
were silently broken. Add the m.relates_to, response answers, and
msc3381 compat fields.
- Replace PollContent with PollEvent (more complete implementation):
  closesAt/expiry, voter names, Checkbox for multi-select, MSC3381
  end-time cutoff, permission check for unauthorized end events
- Add missing PollEvent.css.ts and fix broken MessageEvent import
- Update sendEvent calls in PollEvent to standard project pattern
- Delete PollContent.tsx, usePollTally.ts, usePollTally.test.ts
- Fix poll creator option/question inputs to fill dialog width
Adds a 'Poll ends after' row with preset buttons (No end / 1 day /
3 days / 7 days / 14 days). The selected duration is injected as
closes_at (ms timestamp) into the m.poll.start subtype content,
which PollEvent already reads to show the expiry countdown.
- PollCreator: add 2h/6h/12h hour presets; replace day-only buttons
  with unified preset list (No end/2h/6h/12h/1d/3d/7d/14d) + Custom
  button that reveals a datetime-local input
- BookmarksList: add 1h/3h/6h/1d/3d preset chips above the reminder
  datetime input for quick selection
- Add 'export * from matrix-js-sdk/lib/@types/polls' to src/types/matrix-sdk.ts
  so M_POLL_END, M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE, M_POLL_START are
  accessible via the app's import boundary
- Replace non-existent PollContent import in useTimelineEventRenderer with
  PollEvent from its actual path (features/room/poll/PollEvent)
- Use M_POLL_START from $types/matrix-sdk instead of direct matrix-js-sdk import
- Remove unused injectedExperimentFlags variable from vite.config.ts
@Just-Insane
Copy link
Copy Markdown
Contributor Author

Just-Insane commented May 19, 2026

Thanks for the note! The branch was initially developed on top of my integration branch (which merges all feature branches together for local testing), which bloated the commit/diff count significantly beyond what the feature itself required. The branch has since been rebased onto dev directly with only its own commits, so the diff should now reflect just the polls implementation. Apologies for the noise!

@Just-Insane Just-Insane marked this pull request as ready for review May 19, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

abandoned this pr seems abandoned question Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants