Skip to content

fix(polls): show the author their own results, read NIP-22 comments, cap fan-out - #89

Merged
barrydeen merged 1 commit into
barrydeen:mainfrom
dmnyc:fix/poll-details-and-nip22-comments
Sep 16, 2026
Merged

barrydeen merged 1 commit into
barrydeen:mainfrom
dmnyc:fix/poll-details-and-nip22-comments

Conversation

@dmnyc

@dmnyc dmnyc commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Four things surfaced on one poll. Device tested on an A54. Mirrors barrydeen/wisp#658.

The author couldn't see results on their own poll

showResults gated on hasVoted || isEnded, so the one person who can't vote on a poll — whoever posted it — had no way to see the tally.

What deliberately doesn't change: a reader who hasn't voted still sees options rather than numbers. Showing the tally to everyone was considered and rejected, because the running count sways the vote.

The zap-poll variant gated on hasVoted || isClosed || totalSats > 0, revealing results to everyone the moment any sats landed. It now follows the same rule.

Comment threads rendered as empty

The reply filter and the thread's event gate both admitted kind 1 only. A note whose replies are all NIP-22 comments (kind 1111) showed no replies and a blank count. On the poll that prompted this, every reply was a 1111 — pulling them straight off the relays gives {1111: 5}, not a single kind 1.

Reading comments is all this changes. Composing a reply to a comment still builds a kind-1 event, which NIP-22 forbids — a reply to a comment must itself be kind 1111. That needs the rest of the helper and is left for a follow-up, so expect comments to render correctly but a reply posted to one to thread wrongly elsewhere.

Poll relay fan-out was uncapped

for (url in Nip88.parsePollRelays(poll)) {
    if (url !in sentUrls) relayPool.sendToRelayOrEphemeral(url, msg)
}

Every relay a poll advertises got an ephemeral connection, with no cap — and a poll in the wild advertises 480 of them. Capped at 3 for both poll kinds, matching iOS, which gets complete tallies from that many.

Per-choice voter breakdown

Added to the details drawer: choices ordered by tally descending, each expanding into the list of voters who picked it, tappable through to a profile. The voter data was already being collected — with latest-wins revote handling, so a revote moves someone rather than double-counting — and nothing was reading it.

…cap fan-out

Four fixes that surfaced together on one poll.

The author couldn't see results on their own poll. `showResults` gated on
`hasVoted || isEnded`, so the one person who can't vote on a poll — whoever
posted it — had no way to see the tally. A reader who hasn't voted still sees
options rather than numbers, so the running count can't sway their choice.

The zap-poll variant gated on `hasVoted || isClosed || totalSats > 0`, which
revealed results to everyone as soon as any sats landed. It now follows the
same rule as a normal poll.

Comment threads rendered as empty. The reply filter and the thread's event
gate both admitted kind 1 only, so a note whose replies are all NIP-22
comments (kind 1111) showed no replies and a blank count. Every reply on the
poll that prompted this was a 1111. Reading comments is enough to fix what's
visible; composing a reply to a comment still builds a kind-1 event, which
NIP-22 forbids, and is left for a follow-up along with the rest of the helper.

Poll relay fan-out was uncapped. Every relay a poll advertised got an
ephemeral connection, and a poll in the wild advertises 480 of them. Capped
at 3, matching iOS, which gets complete tallies from that many.

Also adds the per-choice voter breakdown to the details drawer: choices
ordered by tally descending, each expanding into the list of voters who
picked it. The voter data was already being collected with latest-wins revote
handling — nothing was reading it.

@barrydeen barrydeen left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed. getPollVoters is correctly derived from the latest-wins voter state (a revote moves the voter rather than double-counting), the relay-hint cap fixes a real fan-out bug, and the ThreadViewModel change is tree-builder-safe — rebuildTree is kind-agnostic and the parent fallback lands orphaned events at the root. Compiles clean against main.

Two accepted-as-partial notes:

  • Nested NIP-22 replies (comment-on-comment) are still dropped twice over: the relay filter eTags=[rootId] matches lowercase e tags only, and the validation gate in the collector checks lowercase e == rootId. Top-level comments render; replies threaded under a comment won't appear anywhere. Matches the "partial" doc comment on Nip22.kt, just flagging that some threads will still look thin.
  • Dropping totalSats > 0 from zap-poll showResults also hides the running sats tally from readers who haven't voted. Intentional per the PR body; noting it as a behavior change, not a defect.

@barrydeen
barrydeen merged commit 2104a47 into barrydeen:main Sep 16, 2026
@dmnyc

dmnyc commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Amended in 31fb1cf with the two fixes from the review on barrydeen/wisp#658 — both defects were present here too.

Fan-out cap now covers every call site, not just the feed-engagement REQs. MetadataFetcher.requestPollVotes / requestZapPollVotes fire whenever a poll merely renders, which is the path that actually triggers, and SocialActionManager.publishPollVote broadcast a signed vote to every advertised relay. The cap lives in Nip88.MAX_POLL_RELAY_HINTS with capped accessors beside the parsers; the raw parsers are now referenced nowhere outside the two NIP files.

Nested comments were being dropped. Admitting kind 1111 wasn't enough — the root check immediately after required a lowercase e equal to the root, but a NIP-22 comment scopes to its root with an uppercase E and uses lowercase e for its immediate parent. Top-level comments passed, replies to comments were discarded, and threads rendered flat one level deep.

Verified on an emulator against a thread whose 25 comments are all nested — every one would have been dropped by the old gate.

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.

2 participants