From 329ad507f6519414bc4283851e8f3e7bdc227b42 Mon Sep 17 00:00:00 2001
From: Bhavishy
Date: Thu, 2 Apr 2026 11:18:41 +0530
Subject: [PATCH 1/7] feat: use NIP-50 relay-side search with scope selection
---
src/app/components/SpaceSearch.svelte | 137 ++++++++++++++++++++++----
1 file changed, 117 insertions(+), 20 deletions(-)
diff --git a/src/app/components/SpaceSearch.svelte b/src/app/components/SpaceSearch.svelte
index c5557bcb..f25313fe 100644
--- a/src/app/components/SpaceSearch.svelte
+++ b/src/app/components/SpaceSearch.svelte
@@ -1,15 +1,17 @@
+
+
+ {props.event.content || "Poll"}
+ {$results.voters} voter{$results.voters === 1 ? "" : "s"}
+
diff --git a/src/app/components/NoteContentPoll.svelte b/src/app/components/NoteContentPoll.svelte
new file mode 100644
index 00000000..db6af151
--- /dev/null
+++ b/src/app/components/NoteContentPoll.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
{props.event.content || "Poll"}
+
+ {pollType === "multiplechoice" ? "Multiple choice" : "Single choice"}
+ {#if endsAt}
+ · Ends {formatTimestampRelative(endsAt)}
+ {/if}
+ {#if closed}
+ · Closed
+ {/if}
+
+
+
{$results.voters} voter{$results.voters === 1 ? "" : "s"}
+
+
+
+ {#each $results.options as option (option.id)}
+ {@const maxVotes = Math.max(...$results.options.map(item => item.votes), 1)}
+
+
+ {option.label}
+ {option.votes}
+
+
+
+ {/each}
+
+
diff --git a/src/app/components/PollCreate.svelte b/src/app/components/PollCreate.svelte
new file mode 100644
index 00000000..d4c46a30
--- /dev/null
+++ b/src/app/components/PollCreate.svelte
@@ -0,0 +1,166 @@
+
+
+
+
+
+ Create a Poll
+ Ask the room a question with one or more answers.
+
+
+
+ {#snippet label()}
+ Question*
+ {/snippet}
+ {#snippet input()}
+
+
+
+
+ {/snippet}
+
+
+
+ {#snippet label()}
+ Options*
+ {/snippet}
+ {#snippet input()}
+
+ {#each options as option, index (index)}
+
+
+
+
+ removeOption(index)}>
+
+
+
+ {/each}
+
+
+ Add option
+
+
+ {/snippet}
+
+
+
+
+ {#snippet label()}
+ Poll type
+ {/snippet}
+ {#snippet input()}
+
+ Single choice
+ Multiple choice
+
+ {/snippet}
+
+
+ {#snippet label()}
+ Ends at
+ {/snippet}
+ {#snippet input()}
+
+ {/snippet}
+
+
+
+
+
+
+
+ Go back
+
+ Create Poll
+
+
diff --git a/src/app/components/PollItem.svelte b/src/app/components/PollItem.svelte
new file mode 100644
index 00000000..7a5eea1f
--- /dev/null
+++ b/src/app/components/PollItem.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ Posted by
+ {#if h}
+ in
+ {/if}
+
+
+
+
diff --git a/src/app/components/PollVotes.svelte b/src/app/components/PollVotes.svelte
new file mode 100644
index 00000000..ee31e8a6
--- /dev/null
+++ b/src/app/components/PollVotes.svelte
@@ -0,0 +1,126 @@
+
+
+
+
+
+ {pollType === "multiplechoice" ? "Multiple choice" : "Single choice"}
+ {#if endsAt}
+ · Ends {formatTimestampRelative(endsAt)}
+ {/if}
+ {#if closed}
+ · Closed
+ {/if}
+
+
{results.voters} vote{results.voters === 1 ? "" : "s"}
+
+
+
+ {#each options as option (option.id)}
+ {@const maxVotes = Math.max(...results.options.map(result => result.votes), 1)}
+ {@const current = results.options.find(result => result.id === option.id)}
+
+ {/each}
+
+
+ {#if !closed}
+
+ Cast vote
+
+ {/if}
+
diff --git a/src/app/components/SpaceMenu.svelte b/src/app/components/SpaceMenu.svelte
index d7b97c83..fd8d8a5d 100644
--- a/src/app/components/SpaceMenu.svelte
+++ b/src/app/components/SpaceMenu.svelte
@@ -1,6 +1,7 @@
+
+
+ {#snippet title()}
+
+ Polls
+ {/snippet}
+ {#snippet action()}
+
+
+ Create
+
+ {/snippet}
+
+
+
+ {#each items as event (event.id)}
+
+ {/each}
+
+
+ {#if loading}
+ Looking for polls...
+ {:else if items.length === 0}
+ No polls found.
+ {:else}
+ That's all!
+ {/if}
+
+
+
diff --git a/src/routes/spaces/[relay]/polls/[id]/+page.svelte b/src/routes/spaces/[relay]/polls/[id]/+page.svelte
new file mode 100644
index 00000000..eac6f17d
--- /dev/null
+++ b/src/routes/spaces/[relay]/polls/[id]/+page.svelte
@@ -0,0 +1,105 @@
+
+
+
+ {#snippet title()}
+ {$event?.content || "Poll"}
+ {/snippet}
+
+
+
+ {#if $event}
+
+
+
+
+ {#if !showAll && $comments.length > 4}
+
+
+
+ Show all {$comments.length} comments
+
+
+ {/if}
+ {#each $comments.slice(0, showAll ? undefined : 4) as reply (reply.id)}
+
+
+
+
+
+
+ {/each}
+
+ {#if showReply}
+
+ {:else}
+
+ Comment on this poll
+
+ {/if}
+ {:else}
+ {#await sleep(5000)}
+ Loading poll...
+ {:then}
+ Failed to load poll.
+ {/await}
+ {/if}
+
diff --git a/src/routes/spaces/[relay]/recent/+page.svelte b/src/routes/spaces/[relay]/recent/+page.svelte
index cc888f00..b4a61071 100644
--- a/src/routes/spaces/[relay]/recent/+page.svelte
+++ b/src/routes/spaces/[relay]/recent/+page.svelte
@@ -26,8 +26,10 @@
import ClassifiedItem from "@app/components/ClassifiedItem.svelte"
import GoalItem from "@app/components/GoalItem.svelte"
import CalendarEventItem from "@app/components/CalendarEventItem.svelte"
+ import PollItem from "@app/components/PollItem.svelte"
import RecentConversation from "@app/components/RecentConversation.svelte"
import {decodeRelay, deriveEventsForUrl, CONTENT_KINDS} from "@app/core/state"
+ import {Poll} from "nostr-tools/kinds"
const url = decodeRelay($page.params.relay!)
const since = ago(3, MONTH)
@@ -126,6 +128,8 @@
{:else if event.kind === EVENT_TIME}
+ {:else if event.kind === Poll}
+
{:else}
{/if}
--
2.52.0
From 7a44bd1677721da7ebfd6de53ef381defdc7b2d6 Mon Sep 17 00:00:00 2001
From: Bhavishy
Date: Fri, 3 Apr 2026 01:09:00 +0530
Subject: [PATCH 5/7] feat: add NIP-88 poll support
---
src/app/components/NoteContentPoll.svelte | 4 +++-
src/app/components/PollCreate.svelte | 11 +++++++++--
src/app/components/PollItem.svelte | 4 +++-
src/app/components/PollVotes.svelte | 15 +++++++++------
src/routes/spaces/[relay]/polls/+page.svelte | 2 +-
src/routes/spaces/[relay]/polls/[id]/+page.svelte | 6 +++++-
6 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/src/app/components/NoteContentPoll.svelte b/src/app/components/NoteContentPoll.svelte
index db6af151..ffcf9bc3 100644
--- a/src/app/components/NoteContentPoll.svelte
+++ b/src/app/components/NoteContentPoll.svelte
@@ -35,7 +35,9 @@
{/if}
- {$results.voters} voter{$results.voters === 1 ? "" : "s"}
+
+ {$results.voters} voter{$results.voters === 1 ? "" : "s"}
+
diff --git a/src/app/components/PollCreate.svelte b/src/app/components/PollCreate.svelte
index d4c46a30..fb17bf51 100644
--- a/src/app/components/PollCreate.svelte
+++ b/src/app/components/PollCreate.svelte
@@ -118,7 +118,11 @@
{#each options as option, index (index)}
-
+
removeOption(index)}>
@@ -150,7 +154,10 @@
Ends at
{/snippet}
{#snippet input()}
-
+
{/snippet}
diff --git a/src/app/components/PollItem.svelte b/src/app/components/PollItem.svelte
index 7a5eea1f..1f96e96a 100644
--- a/src/app/components/PollItem.svelte
+++ b/src/app/components/PollItem.svelte
@@ -18,7 +18,9 @@
const h = getTagValue("h", event.tags)
-
+
diff --git a/src/app/components/PollVotes.svelte b/src/app/components/PollVotes.svelte
index ee31e8a6..c334f9b9 100644
--- a/src/app/components/PollVotes.svelte
+++ b/src/app/components/PollVotes.svelte
@@ -33,10 +33,11 @@
const endsAt = getPollEndsAt(event)
const results = $derived.by(() => getPollResults(event, $responses))
- const ownResponse = $derived.by(() =>
- $responses
- .filter(response => response.pubkey === $pubkey)
- .sort((left, right) => right.created_at - left.created_at)[0],
+ const ownResponse = $derived.by(
+ () =>
+ $responses
+ .filter(response => response.pubkey === $pubkey)
+ .sort((left, right) => right.created_at - left.created_at)[0],
)
const submit = async () => {
@@ -111,9 +112,11 @@
{/if}
{option.label}
- {current?.votes || 0} vote{(current?.votes || 0) === 1 ? "" : "s"}
+ {current?.votes || 0} vote{(current?.votes || 0) === 1 ? "" : "s"}
-
+
{/each}
diff --git a/src/routes/spaces/[relay]/polls/+page.svelte b/src/routes/spaces/[relay]/polls/+page.svelte
index c875ad9f..c5ac4b2d 100644
--- a/src/routes/spaces/[relay]/polls/+page.svelte
+++ b/src/routes/spaces/[relay]/polls/+page.svelte
@@ -5,7 +5,7 @@
import {page} from "$app/stores"
import {sortBy, partition, spec, pushToMapKey, max} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
- import {COMMENT, getTagValue} from "@welshman/util"
+ import {getTagValue} from "@welshman/util"
import {fly} from "@lib/transition"
import PollIcon from "@assets/icons/revote.svg?dataurl"
import Add from "@assets/icons/add.svg?dataurl"
diff --git a/src/routes/spaces/[relay]/polls/[id]/+page.svelte b/src/routes/spaces/[relay]/polls/[id]/+page.svelte
index eac6f17d..7de2c560 100644
--- a/src/routes/spaces/[relay]/polls/[id]/+page.svelte
+++ b/src/routes/spaces/[relay]/polls/[id]/+page.svelte
@@ -47,7 +47,11 @@
onMount(() => {
const controller = new AbortController()
- request({relays: [url], filters: [{kinds: [Poll], ids: [id]}, {kinds: [PollResponse], "#e": [id]}, ...filters], signal: controller.signal})
+ request({
+ relays: [url],
+ filters: [{kinds: [Poll], ids: [id]}, {kinds: [PollResponse], "#e": [id]}, ...filters],
+ signal: controller.signal,
+ })
return () => {
controller.abort()
--
2.52.0
From 70ce54c5a503fa25c6c7e4642f057e8c2dac4e55 Mon Sep 17 00:00:00 2001
From: Bhavishy
Date: Fri, 3 Apr 2026 04:04:45 +0530
Subject: [PATCH 6/7] fix: refine poll UX and review and fix requested changes
---
src/app/components/ComposeMenu.svelte | 2 +-
.../components/NoteContentMinimalPoll.svelte | 9 +-
src/app/components/NoteContentPoll.svelte | 58 +++------
src/app/components/PollCreate.svelte | 111 ++++++++++++++----
src/app/components/PollVotes.svelte | 42 ++++---
src/app/util/polls.ts | 20 ++--
.../spaces/[relay]/polls/[id]/+page.svelte | 2 -
7 files changed, 142 insertions(+), 102 deletions(-)
diff --git a/src/app/components/ComposeMenu.svelte b/src/app/components/ComposeMenu.svelte
index 8c89e9f4..b7828d55 100644
--- a/src/app/components/ComposeMenu.svelte
+++ b/src/app/components/ComposeMenu.svelte
@@ -67,7 +67,7 @@
- Poll
+ Ask a Question
diff --git a/src/app/components/NoteContentMinimalPoll.svelte b/src/app/components/NoteContentMinimalPoll.svelte
index 6e479710..1d67365e 100644
--- a/src/app/components/NoteContentMinimalPoll.svelte
+++ b/src/app/components/NoteContentMinimalPoll.svelte
@@ -2,21 +2,18 @@
import type {ComponentProps} from "svelte"
import {derived} from "svelte/store"
import {PollResponse} from "nostr-tools/kinds"
- import {repository} from "@welshman/app"
- import {deriveArray, deriveEventsById} from "@welshman/store"
import ContentMinimal from "@app/components/ContentMinimal.svelte"
+ import {deriveEvents} from "@app/core/state"
import {getPollResults} from "@app/util/polls"
const props: ComponentProps = $props()
- const responses = deriveArray(
- deriveEventsById({repository, filters: [{kinds: [PollResponse], "#e": [props.event.id]}]}),
- )
+ const responses = deriveEvents([{kinds: [PollResponse], "#e": [props.event.id]}])
const results = derived(responses, $responses => getPollResults(props.event, $responses))
-
+
{props.event.content || "Poll"}
{$results.voters} voter{$results.voters === 1 ? "" : "s"}
diff --git a/src/app/components/NoteContentPoll.svelte b/src/app/components/NoteContentPoll.svelte
index ffcf9bc3..53fcb203 100644
--- a/src/app/components/NoteContentPoll.svelte
+++ b/src/app/components/NoteContentPoll.svelte
@@ -1,55 +1,29 @@
-
-
-
{props.event.content || "Poll"}
-
- {pollType === "multiplechoice" ? "Multiple choice" : "Single choice"}
- {#if endsAt}
- · Ends {formatTimestampRelative(endsAt)}
- {/if}
- {#if closed}
- · Closed
- {/if}
-
-
-
- {$results.voters} voter{$results.voters === 1 ? "" : "s"}
-
-
+
{props.event.content || "Poll"}
-
- {#each $results.options as option (option.id)}
- {@const maxVotes = Math.max(...$results.options.map(item => item.votes), 1)}
-
-
- {option.label}
- {option.votes}
-
-
-
- {/each}
-
+ {#if props.url}
+
+ {/if}
diff --git a/src/app/components/PollCreate.svelte b/src/app/components/PollCreate.svelte
index fb17bf51..33d3cc3e 100644
--- a/src/app/components/PollCreate.svelte
+++ b/src/app/components/PollCreate.svelte
@@ -1,15 +1,17 @@
Create a Poll
- Ask the room a question with one or more answers.
+ Ask a question and collect votes right in the feed.
@@ -114,22 +171,33 @@
Options*
{/snippet}
{#snippet input()}
-
- {#each options as option, index (index)}
-
+
diff --git a/src/app/components/PollVotes.svelte b/src/app/components/PollVotes.svelte
index c334f9b9..d134f384 100644
--- a/src/app/components/PollVotes.svelte
+++ b/src/app/components/PollVotes.svelte
@@ -1,10 +1,10 @@
- {props.event.content || "Poll"}
+
{$results.voters} voter{$results.voters === 1 ? "" : "s"}
diff --git a/src/app/components/NoteContentPoll.svelte b/src/app/components/NoteContentPoll.svelte
index 53fcb203..a60ecda4 100644
--- a/src/app/components/NoteContentPoll.svelte
+++ b/src/app/components/NoteContentPoll.svelte
@@ -21,7 +21,7 @@
-
{props.event.content || "Poll"}
+
{#if props.url}
diff --git a/src/app/components/PollVotes.svelte b/src/app/components/PollVotes.svelte
index d134f384..e86f4412 100644
--- a/src/app/components/PollVotes.svelte
+++ b/src/app/components/PollVotes.svelte
@@ -1,12 +1,14 @@
-
+
-
- {#if !closed}
-
- Cast vote
-
- {/if}
--
2.52.0