Skip to content

feat: display listings from nostr - #38

Open
lambdakilo wants to merge 3 commits into
mainfrom
37/nostr-events-landing-page
Open

feat: display listings from nostr#38
lambdakilo wants to merge 3 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 its author has retracted, which has to happen client side because
relay deletion support is advisory. 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.

@nostr-dev-kit/ndk-svelte was rejected despite being the obvious fit: its subscription helper
leaves the old copy in place when an edit arrives, so editing a bounty duplicates it permanently,
and its deletion handling checks a non-standard tag rather than nip-09. worth knowing about ndk
proper too: its guardrails throw rather than warn, and their fetchevents check does not exempt
kind 30050, so enabling them outside production would start failing bounty queries.

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.

Related issue

Closes #37

Changes made

  • src/lib/bounty.ts: the read side, next to the existing publish side. collapses the copies each
    relay returns into one card, since subscribe() dedupes only by event id and an addressable event
    gets a new id on every edit. the parser rejects rather than coerces, because one event with a
    bogus status tag would otherwise blank the page, and it requires only the tags the ui cannot
    render without, since our own publisher omits several the spec asks for. all pure functions, so
    the parts most likely to be wrong are unit tested.
  • src/routes/+page.svelte: subscribes on mount, with loading, empty and relay-timeout states. the
    query is narrowed to events carrying a bounty status, because kind 30050 is a shared namespace
    and unrelated apps' traffic would otherwise push real bounties out of the query window: same 2
    bounties either way, raw events down from 101 to 20.
  • src/lib/types/bounty.ts: a bounty now carries the address that identifies it across edits, plus
    the author and timestamp retraction matching needs.
  • src/lib/components/BountyCard.svelte: links by address instead of event id. Single bounty page UI #35 introduced the
    link and the rebase took it silently, so every edit would have broken that bounty's own link.
  • src/lib/mock/bounties.ts, src/routes/bounties/[id]/+page.svelte: the still-mocked detail page
    keeps working with the widened type.
  • src/lib/bounty.test.ts: 41 tests, up from 6.

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
  • set offline mode in developer tools and confirm the spinner resolves to the empty state within 8s as no bounties found on the relays

@lambdakilo
lambdakilo force-pushed the 37/nostr-events-landing-page branch from cfaa59e to a84e50f Compare August 15, 2026 10:58
@lambdakilo
lambdakilo marked this pull request as ready for review August 19, 2026 10:24
@lambdakilo
lambdakilo requested review from guildm4ster and spherical-spinach and a lite review from Copilot August 19, 2026 10:24
@lambdakilo

Copy link
Copy Markdown
Member Author

i hope i did the subscription feature correctly in ndk instead of using ndk-svelte since i recall guildmaster saying something about that particular function

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR switches the landing page bounty listing from mock data to live Nostr kind:30050 events via NDK subscriptions, adding client-side parsing/deduplication and deletion handling so edited bounties collapse into a single card and retracted bounties are hidden.

Changes:

  • Add a read/parse layer for kind:30050 bounty events, including dedupe-by-address and NIP-09 deletion tracking.
  • Update the landing page to subscribe to relays on mount and render loading/empty states plus live bounty cards keyed by stable address.
  • Extend the Bounty type (address + optional check-in interval) and update mocks/UI + expand unit tests substantially.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/lib/bounty.ts Implements parsing, deduping, deletion tracking, and visible-bounty selection for relay events.
src/routes/+page.svelte Replaces mock listing with an NDK subscription and adds loading/empty/timeout UI states.
src/lib/types/bounty.ts Extends Bounty with stable address and makes checkInIntervalDays optional.
src/lib/components/BountyCard.svelte Links bounty cards using the stable bounty address (URL-encoded).
src/lib/mock/bounties.ts Updates mock bounties and lookup to use address-based routing.
src/routes/bounties/[id]/+page.svelte Adjusts detail page rendering for optional check-in interval and clarifies mock-only behavior.
src/lib/bounty.test.ts Adds broad unit coverage for parsing, dedupe, and deletion visibility logic.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/lib/bounty.ts
Comment on lines +110 to +116
const { id, pubkey, created_at: createdAt } = event;
const title = event.tagValue('title')?.trim();
const status = event.tagValue('s');
const resolutionMode = event.tagValue('resolution_mode');

if (!id || !pubkey || !createdAt || !event.dTag || !title) return null;
if (!isBountyStatus(status)) return null;
Comment thread src/lib/bounty.ts
Comment on lines +187 to +199
const { pubkey, created_at: at } = event;
if (!pubkey || !at) return;

const targets = [
...event.getMatchingTags('a'),
...event.getMatchingTags('e')
];
for (const [, target] of targets) {
if (!target) continue;
const current = deletions.get(target);
if (!current || at > current.at) {
deletions.set(target, { at, by: pubkey });
}
Comment thread src/routes/+page.svelte
Comment on lines +34 to +41
// One subscription, one EOSE. `#s` keeps other apps' kind-30050 events out
// of the limit; the deletion filter uses the `k` tag NDKEvent.delete()
// writes, so it only matches retracted bounties.
const sub = ndk().subscribe(
[
{ kinds: [BOUNTY_KIND], '#s': [...BOUNTY_STATUSES], limit: 100 },
{ kinds: [NDKKind.EventDeletion], '#k': [String(BOUNTY_KIND)] }
],
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

2 participants