Skip to content

feat(meet): add interactive polling to meeting chat#76

Open
dankgarlic1 wants to merge 3 commits into
frappe:developfrom
dankgarlic1:feat/meet/polling-in-meeting-chat
Open

feat(meet): add interactive polling to meeting chat#76
dankgarlic1 wants to merge 3 commits into
frappe:developfrom
dankgarlic1:feat/meet/polling-in-meeting-chat

Conversation

@dankgarlic1

@dankgarlic1 dankgarlic1 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

This ports the new polling feature into the Frappe Suite monorepo architecture

closes #93

JAM: https://jam.dev/c/c42e3c51-6258-4f16-99b5-f8bff43f2a39

REF: frappe/meet#62 (comment)

This ports the new polling feature into the Frappe Suite monorepo architecture and aligns the UI with the Espresso design system.

- Ported SFU server poll handlers and registry logic to the nested backend
- Added CreatePollModal and PollMessageCard to the unified Vite frontend
- Removed custom colors and shadows to strictly match the Espresso grayscale UI
- Replaced deprecated Feather icons with Lucide icons (lucide-bar-chart-2)
- Wired up real-time socket events and Pinia store for live voting updates
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

These issues should be fixed before merging.

  • Frontend type checking can resolve server-only SFU types through the new poll DTO import.
  • Poll creation can broadcast options that do not match the client payload contract.
  • A participant who already voted can lose that state after refresh or rejoin.

usePoll.ts, usePollStore.ts, PollHandlers.ts, PollMessageCard.vue

Important Files Changed

Filename Overview
frontend/src/apps/meet/composables/usePoll.ts Adds poll socket event wiring and requests, but imports poll DTOs from server-only source.
suite/meet/sfu-server/src/server/handlers/PollHandlers.ts Adds poll create and vote handling, with remaining validation gaps for option text shape.
frontend/src/apps/meet/components/PollMessageCard.vue Adds poll voting UI, but vote state is not restored after refresh or rejoin.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
frontend/src/apps/meet/composables/usePoll.ts:5
**Server Type Import** This imports the poll DTO from `sfu-server/src/types/index.ts`, which also imports server-only `mediasoup/node/*` types and augments `socket.io`. The frontend package only depends on `mediasoup-client` and `socket.io-client`, so frontend type checking can fail when it resolves this server source file. Move `PollPayloadFE` to a frontend-safe shared type module and import that here and in `usePollStore.ts`.

### Issue 2 of 3
suite/meet/sfu-server/src/server/handlers/PollHandlers.ts:48-52
**Validate Option Text** The handler now bounds the number of options, but it still maps raw client objects without checking `opt.text`. A host can send `options: [{ text: "A" }, {}]`; the server stores and broadcasts an option with missing text, so clients receive a poll that violates the required `{ id, text, votes }` shape. Validate every option is a non-empty string before creating the poll.

### Issue 3 of 3
frontend/src/apps/meet/components/PollMessageCard.vue:58
**Lost Vote State** This state is only local to the card, while `existing_polls` and `poll:update` do not include whether the current participant already voted. When a user votes, refreshes, and rejoins, the card renders all options as clickable; the server rejects the duplicate vote, the catch resets this back to `null`, and the user stays in an endless unvoted state. Include current-user vote state in the poll payload or derive it before rendering the card.

Reviews (2): Last reviewed commit: "fix: addressed greptile comments" | Re-trigger Greptile

Comment thread suite/meet/sfu-server/src/server/handlers/PollHandlers.ts
Comment thread suite/meet/sfu-server/src/server/handlers/PollHandlers.ts
Comment thread suite/meet/sfu-server/src/server/handlers/PollHandlers.ts
Comment on lines +31 to +34
sfuClient.on("existing_polls", (data: unknown) => {
const payload = data as { polls: PollPayloadFE[] };
pollStore.setExistingPolls(payload.polls);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Existing Polls Never Sync

The client registers an existing_polls listener, but the server never emits that event when a participant joins. Anyone who joins after a poll was created starts with an empty poll store and cannot see or vote on the active poll until a new poll event occurs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/apps/meet/composables/usePoll.ts
Line: 31-34

Comment:
**Existing Polls Never Sync**

The client registers an `existing_polls` listener, but the server never emits that event when a participant joins. Anyone who joins after a poll was created starts with an empty poll store and cannot see or vote on the active poll until a new poll event occurs.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread suite/meet/sfu-server/src/server/RoomRegistry.ts
Comment thread frontend/src/apps/meet/components/PollMessageCard.vue Outdated
import type { SFUClient } from "../utils/SFUClient";
import type { CurrentUser } from "./useCurrentUser";
import { usePollStore } from "./usePollStore";
import type { PollPayloadFE } from "../../../../../suite/meet/sfu-server/src/types"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Server Type Import This imports the poll DTO from sfu-server/src/types/index.ts, which also imports server-only mediasoup/node/* types and augments socket.io. The frontend package only depends on mediasoup-client and socket.io-client, so frontend type checking can fail when it resolves this server source file. Move PollPayloadFE to a frontend-safe shared type module and import that here and in usePollStore.ts.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/apps/meet/composables/usePoll.ts
Line: 5

Comment:
**Server Type Import** This imports the poll DTO from `sfu-server/src/types/index.ts`, which also imports server-only `mediasoup/node/*` types and augments `socket.io`. The frontend package only depends on `mediasoup-client` and `socket.io-client`, so frontend type checking can fail when it resolves this server source file. Move `PollPayloadFE` to a frontend-safe shared type module and import that here and in `usePollStore.ts`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +48 to +52
options: options.map((opt: { id?: string; text: string }) => ({
id: `opt-${crypto.randomUUID()}`,
text: opt.text,
votes: 0,
})),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Validate Option Text The handler now bounds the number of options, but it still maps raw client objects without checking opt.text. A host can send options: [{ text: "A" }, {}]; the server stores and broadcasts an option with missing text, so clients receive a poll that violates the required { id, text, votes } shape. Validate every option is a non-empty string before creating the poll.

Prompt To Fix With AI
This is a comment left during a code review.
Path: suite/meet/sfu-server/src/server/handlers/PollHandlers.ts
Line: 48-52

Comment:
**Validate Option Text** The handler now bounds the number of options, but it still maps raw client objects without checking `opt.text`. A host can send `options: [{ text: "A" }, {}]`; the server stores and broadcasts an option with missing text, so clients receive a poll that violates the required `{ id, text, votes }` shape. Validate every option is a non-empty string before creating the poll.

How can I resolve this? If you propose a fix, please make it concise.


const pollService = inject("poll") as any;

const localVotedOption = ref<string | null>(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Lost Vote State This state is only local to the card, while existing_polls and poll:update do not include whether the current participant already voted. When a user votes, refreshes, and rejoins, the card renders all options as clickable; the server rejects the duplicate vote, the catch resets this back to null, and the user stays in an endless unvoted state. Include current-user vote state in the poll payload or derive it before rendering the card.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/apps/meet/components/PollMessageCard.vue
Line: 58

Comment:
**Lost Vote State** This state is only local to the card, while `existing_polls` and `poll:update` do not include whether the current participant already voted. When a user votes, refreshes, and rejoins, the card renders all options as clickable; the server rejects the duplicate vote, the catch resets this back to `null`, and the user stays in an endless unvoted state. Include current-user vote state in the poll payload or derive it before rendering the card.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Real-time Polling via Control Bar with In-Chat Voting

2 participants