feat: display listings from nostr - #38
Draft
lambdakilo wants to merge 2 commits into
Draft
Conversation
lambdakilo
force-pushed
the
37/nostr-events-landing-page
branch
from
August 15, 2026 10:58
cfaa59e to
a84e50f
Compare
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.
Summary
prompt for opus 4.8: display nostr events in listing view i.e. the landing page. the code should
be as minimal as possible and as close to official documentation and best practices as possible
because its production code and not poc. i think the bounties should be fetched from the relays.
might be hard to remove the asdfasdf note i sent already to some relays.
the landing page rendered three hardcoded bounties. the app could already write kind:30050 events
at /bounties/new but never read them back. it now subscribes to the relays and renders live
bounties, dropping anything a maker has retracted. scope is the data source only, no search or
filter ui.
rebased onto origin/main, which brought in #35 (single bounty page ui). #35 changed both the card
and the bounty type, so this branch adopts those rather than keeping its own.
Related issue
Closes #37
Changes
address(30050:<pubkey>:<d>), makerpubkey andcreatedat, plus Single bounty page UI #35's description, resolutionmode and checkinintervaldays. address keys the list
and survives revisions; the rest drive nip-09 matching.
upsertbounty, recorddeletion, visiblebounties, all pure so the risky logic is unit testable.
relay-timeout states.
detail page still resolves.
parsebountyevent.
Reasoning
dedupe is ours to do. ndk's subscribe() dedupes only by event id, so two relays holding different
revisions of one addressable bounty deliver two events. we collapse by kind:pubkey:d, newest wins.
the same id-versus-address distinction is why the card now links by address: #35 wrapped it in a
link to
/bounties/${bounty.id}, and since this branch never touched that file the rebase tookthat version silently, so every edit to a bounty would have broken its own link.
@nostr-dev-kit/ndk-svelte was rejected despite being the obvious fit. its $subscribe splices a
newer revision into the list without removing the old one, so editing a bounty duplicates it
permanently, and its skipdeleted checks a non-standard ["deleted"] tag rather than nip-09.
ndk.subscribe(filter, { onevent }) is what ndk's own guardrails recommend for feeds anyway.
the parser rejects rather than coerces. relay data is untrusted, and the satcode-demo reference
parser casts the s tag with
as bountystatus, so an event carrying ["s","pwned"] would reach thecard's status lookup and blank the page. resolution_mode goes through the same kind of guard,
since an unrecognized mode falls through the detail page's {:else} and would mislabel the bounty
as mode a. required tags are limited to the ones the ui cannot render without, because
buildbountyevent omits spec-required gov_key_*, refund_address and check_in_days and a parser that
demanded those would reject our own writes. that is also why checkinintervaldays is optional.
kind 30050 is contested namespace. verified against live relays before merging:
{ kinds: [30050], limit: 100 }returned 157 events of which 2 were bounties, the rest being unrelated apps(encrypted key bundles, chat rooms, device handshakes). the parser dropped all 155 cleanly, but the
limit was the real problem, since relays return the most recent kind:30050 events and genuine
bounties get pushed out of the window as that traffic grows. adding
'#s': [...bounty_statuses]narrows to events carrying a bounty status. measured side by side on live relays: same 2 bounties,
raw events down from 101 to 20.
nip-09 is honoured client side. a second filter in the same subscription picks up kind:5 events
tagged k=30050, matched by address or event id and only when signed by the bounty's own author.
relay deletion support is advisory, so handling it in the client makes retraction reliable.
onmount rather than $effect: it never runs during ssr, so ndk is only constructed in the browser,
it never tracks reads, and its return value is the teardown. unreachable relays never send eose,
so an 8s timeout releases the spinner.
the detail page is still mock-backed, so a card for a real relay bounty lands on "bounty not
found". that is where main already is rather than a regression from this branch, and it wants a
follow-up issue.
Author checklist
bun run prruns all the commands below in one go.bun run format- code has been formattedbun run lint- no prettier/eslint errorsbun run check- no svelte-check/TypeScript errorsbun run test- all tests passbun run build- build succeedsReviewer manual testing checklist
without a reload
than duplicating
honour it themselves