diff --git a/commons/plans/2026-08-03-poll-results-page.md b/commons/plans/2026-08-03-poll-results-page.md index 0cb3c8d17c..2a1bd9e142 100644 --- a/commons/plans/2026-08-03-poll-results-page.md +++ b/commons/plans/2026-08-03-poll-results-page.md @@ -12,6 +12,12 @@ consumers: amethyst, desktopApp > **Status:** proposal. Nothing implemented. This document surveys what exists, > names four correctness bugs the current tally has, and specifies a shared > results surface for Android and Desktop. _Authored 2026-08-03._ +> +> **Visual mockup:** [`assets/2026-08-03-poll-results-page.html`](assets/2026-08-03-poll-results-page.html) +> — annotated screens for the Android page, the Desktop deck column, the loading / +> closed / empty states, and a side-by-side of the two multi-choice percentage +> readings from §10.1. Open it in a browser; it renders in Amethyst's own theme +> tokens and follows the viewer's light/dark preference. ## 1. Motivation diff --git a/commons/plans/assets/2026-08-03-poll-results-page.html b/commons/plans/assets/2026-08-03-poll-results-page.html new file mode 100644 index 0000000000..20524de2f1 --- /dev/null +++ b/commons/plans/assets/2026-08-03-poll-results-page.html @@ -0,0 +1,1861 @@ +
+ A poll in Amethyst currently ends its life as a percentage bar and four avatars in the feed. + This proposes a dedicated results screen that answers the two questions people actually ask — + how many votes each answer got, and who voted for what — shared between Android and Desktop. +
+ ++ The inline card is deliberately compact, and that compactness is the whole problem: no vote + counts anywhere, at most four voter avatars, and no route to the fifth. Desktop already grew a + voter popup to work around it, which is the demand showing up as a divergence. +
+
+ The +171 chip is a dead end — it counts people you cannot reach. Percentages appear
+ without their counts, and on a multiple-choice poll the denominator is wrong
+ (PollResponsesCache.kt:52).
+
+ Two changes to the card itself: a vote count beside the percentage, and the privacy + line placed where the choice is made rather than after it. The avatar stack and the + count both become the entry point to the full results screen. +
++ Summary above detail: totals, then per-option breakdown, then every voter with the option they + chose on the same row. Try the option chips — they filter the list below. +
+
+ The tally is already a live StateFlow — new votes animate in without asking.
+ The refresh action re-runs the backfill against the poll's own relays, which is the only
+ part that can be stale.
+
+ Voters is distinct pubkeys; selections is how many boxes were ticked. They + are identical on a single-choice poll and diverge on multiple choice — which is exactly + where today's percentages go wrong (§03). +
+
+ Each row gains N of M voters. The winner keeps the existing green treatment
+ from RenderClosedItem so the screen reads as the card it came
+ from, and the bar keeps the same 800 ms tween.
+
+ Desktop already allows re-voting; Android does not. This strip makes the behaviour the + same on both, and puts your vote where you don't have to hunt the list for yourself. +
++ Option chips scope the voter list only — the bars above stay put, so the totals can't + appear to change while you browse. Audience is Everyone / Following / Following + WoT, + reusing the follow set the tally already sorts by. +
+
+ The default is a flat list of everyone, so "who voted for what" is answered without
+ drilling into an option. You are pinned first, follows next — that ordering already
+ exists in filterTo. Multiple-choice rows list every option
+ that person picked.
+
+ A NIP-45 count against the poll's relays gives the "~412" — so a partial
+ tally can admit it instead of presenting itself as final. The second line accounts for
+ every response that was dropped and why.
+
+ Same poll, same votes, two readings. 240 people voted and ticked 456 boxes between them. + This is the one decision that blocks the tally work, because it changes the numbers on the + existing feed card too. +
++ Every one of these is a moment where a number could look like a bug unless the screen says + what happened. +
+
+ Desktop navigates by DeckColumnType, not routes, so results open as a
+ column beside the feed. The existing voter popup stays as the quick peek and grows a
+ See all voters footer that opens this column.
+
+ The results page would inherit all of these, so they land first. The four tally bugs share one
+ root cause — ResponseTally never sees the
+ PollEvent, so it cannot know the poll's type, its valid option codes,
+ or its window. The fifth is separate: Android asks the wrong relays for votes. Both fixes
+ improve the feed card on their own.
+
| What breaks | +Impact | +Consequence | +Where | +
|---|---|---|---|
| Multiple-choice denominator | +wrong number | +A voter who picks three options counts three times. Desktop's footer and its own bars already disagree about this. | +PollResponsesCache.kt:52 | +
Every response tag counted |
+ spec | +NIP-88 keeps only the first tag on a single-choice poll. One malformed vote lands in every bucket. | +PollResponsesCache.kt:129 | +
| Votes after the deadline | +spec | +Only createdAt is compared, so a late vote beats a valid earlier one and closed polls keep moving. |
+ Note.kt:1685 | +
| Unknown option codes | +wrong number | +A code that isn't in the poll creates a phantom bucket that still drags every real percentage down. | +PollResponsesCache.kt:129 | +
| Poll relays never queried | +missing data | +Android reads votes from the author's inbox relays, not the relays the poll publishes votes to — so its tallies are short. Desktop patched this per-card. | +FilterRepliesAndReactionsToNotes.kt:89 | +
The first two phases are bug fixes wearing a feature's clothes — worth shipping even if the screen never gets built.
+PollTallyPolicy in commons + tests. Fixes all four numbers above.fetchAllPages backfill.