feat(meet): add interactive polling to meeting chat#76
Conversation
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
Confidence Score: 4/5These issues should be fixed before merging.
usePoll.ts, usePollStore.ts, PollHandlers.ts, PollMessageCard.vue
|
| 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
| sfuClient.on("existing_polls", (data: unknown) => { | ||
| const payload = data as { polls: PollPayloadFE[] }; | ||
| pollStore.setExistingPolls(payload.polls); | ||
| }); |
There was a problem hiding this comment.
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.| 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" |
There was a problem hiding this 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.
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.| options: options.map((opt: { id?: string; text: string }) => ({ | ||
| id: `opt-${crypto.randomUUID()}`, | ||
| text: opt.text, | ||
| votes: 0, | ||
| })), |
There was a problem hiding this 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.
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); |
There was a problem hiding this 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.
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.
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)