Skip to content

feat: display listings from nostr - #38

Draft
lambdakilo wants to merge 2 commits into
mainfrom
37/nostr-events-landing-page
Draft

feat: display listings from nostr#38
lambdakilo wants to merge 2 commits into
mainfrom
37/nostr-events-landing-page

Conversation

@lambdakilo

@lambdakilo lambdakilo commented Aug 14, 2026

Copy link
Copy Markdown
Member

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

  • src/lib/types/bounty.ts: bounty gains id, address (30050:<pubkey>:<d>), makerpubkey and
    createdat, plus Single bounty page UI #35's description, resolutionmode and checkinintervaldays. address keys the list
    and survives revisions; the rest drive nip-09 matching.
  • src/lib/bounty.ts: read side added next to the existing write side. parsebountyevent,
    upsertbounty, recorddeletion, visiblebounties, all pure so the risky logic is unit testable.
  • src/routes/+page.svelte: onmount subscription, sveltemap plus $derived, and loading / empty /
    relay-timeout states.
  • src/lib/components/BountyCard.svelte: links by address instead of event id.
  • src/lib/mock/bounties.ts: the three mocks gain an address and getbounty matches on it, so Single bounty page UI #35's
    detail page still resolves.
  • src/lib/bounty.test.ts: 41 tests, up from 6, including a round trip of buildbountyevent through
    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 took
that 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 the
card'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 pr runs all the commands below in one go.

  • Matches the spec
  • bun run format - code has been formatted
  • bun run lint - no prettier/eslint errors
  • bun run check - no svelte-check/TypeScript errors
  • bun run test - all tests pass
  • bun run build - build succeeds
  • I tested the relevant changes manually

Reviewer manual testing checklist

  • log in with a nip-07 extension, publish a bounty at /bounties/new, confirm it appears on /
    without a reload
  • republish with the same d tag and a changed title, confirm the card updates in place rather
    than duplicating
  • publish a kind:5 deletion for that bounty, confirm it drops off / whether or not the relays
    honour it themselves
  • load / offline, confirm the spinner resolves to the empty state within 8s

@lambdakilo
lambdakilo force-pushed the 37/nostr-events-landing-page branch from cfaa59e to a84e50f Compare August 15, 2026 10:58
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.

Display nostr events on listing view

1 participant