fix(polls): a poll never reached the phone - #310
Open
WhichPaths wants to merge 1 commit into
Open
WhichPaths wants to merge 1 commit into
WhichPaths wants to merge 1 commit into
Conversation
createPoll commits a kind='poll' row and enqueues the same message.new broadcast every other sender enqueues, and then stops. dispatchMessagePush has four call sites -- the HTTP message route, cmdReply, startPrivateChat and startPulledGroup -- and neither poll producer (POST /api/polls, `cumora poll create`) is among them. It was the last human-visible message kind that never reached a phone. This is an asymmetry, not a missing feature. NotificationToasts skips only `kind === 'system'`, so a poll already toasts on desktop and in the Electron notification window, and Message.tsx renders the poll bubble on both shells -- so a phone-only member could see and vote on a poll nobody ever told them about. polls.ts says what the shadow body is for in its own comment: "notifications, search index, plain-text logs". A body built for a notification that was never sent. This is the gap my own yetone#241 left. Its commit message claimed the set was complete -- "Every other INSERT INTO messages in the server is correct as it stands: membership.ts, calendar.ts and inproc-client.ts write kind='system'" -- and polls.ts writes kind='poll', so it fell outside that check. I looked at it at the time and set it aside as a product question, whether a poll is worth a phone buzz. That was the wrong call: the desktop already answers yes, and the code documents the intent. Recipients need no new filtering: computeMessageRecipients joins `users`, so agents are never notified, and the mute and "currently looking at the app" filters apply unchanged. A test pins the mute case.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
createPollcommits akind='poll'row and enqueues the samemessage.newbroadcast every other sender enqueues — and then stops.dispatchMessagePushhas four call sites:POST /conversations/:id/messagesserver/src/api/router.ts:4306cmdReplyserver/src/agents/cli.ts:2421startPrivateChatserver/src/agents/private_chat.ts:249startPulledGroupserver/src/agents/scanner_helper.ts:152Both poll producers —
POST /api/pollsandcumora poll create— funnel intocreatePolland are in neither list. It was the last human-visible message kind that never reached a phone.This is an asymmetry, not a missing feature.
NotificationToastsskips onlykind === 'system':so a poll already toasts on desktop and in the Electron notification window, and
Message.tsxrenders the poll bubble on both shells. A phone-only member could see and vote on a poll nobody ever told them about. Andpolls.tssays what the shadow body is for, in its own comment:A body built for a notification that was never sent.
How often: every poll posted while a recipient's phone is their only live surface. Polls are a first-class composer action and agents post them too —
turn.tshas a whole wake path so a poll author can nudge laggards for missing votes, which makes it worse: the agent keeps poking a room whose members were never told there was a poll.This is a gap my own #241 left
That commit claimed the set was complete:
polls.tswriteskind='poll', so it fell outside that check. I did look at it at the time and set it aside as a product question — whether a poll is worth a phone buzz. That was the wrong call: the desktop already answers yes, and the code documents the intent.The fix
One fire-and-forget dispatch after
COMMIT, matching how the other four do it:Recipients need no new filtering:
computeMessageRecipientsjoinsusers, so agents are never notified, and the mute / "currently looking at the app" filters apply unchanged.Tests
Two added to
agent-reply-push.test.ts, which already owns this contract and its seed helpers:a poll reaches the phone— asserts the recipient is the offline human and that the notification body names the questiona poll obeys the same recipient filters— the guard rail: a new dispatch must not add a delivery path that ignores muteThe second passes either way, on purpose.
Also green:
tsc --noEmit,biome lint ., all threescripts/guard-*.mjs, the unit suite (1502 tests, 0 failures), and thepollsintegration suite (8/8).