From 0dab77b9dd4dc182fad146108677e8c03ad1e605 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 19 Aug 2025 00:21:31 -0500 Subject: [PATCH] Improve NDK state sharing - Use SvelteKit context instead of stores to share NDK instance (see https://svelte.dev/docs/svelte/context) - Clean up some dead code - Move some standalone CSS into `app.css` --- src/app.css | 5 ++- src/lib/components/CommentViewer.svelte | 16 ++++---- src/lib/components/EventInput.svelte | 9 ++--- src/lib/components/EventSearch.svelte | 13 +++---- src/lib/components/LoginModal.svelte | 5 ++- src/lib/components/Notifications.svelte | 14 +++---- src/lib/components/RelayActions.svelte | 22 ++--------- src/lib/components/RelayDisplay.svelte | 9 +---- src/lib/components/RelayStatus.svelte | 8 ++-- .../embedded_events/EmbeddedEvent.svelte | 6 +-- .../embedded_events/EmbeddedSnippets.svelte | 14 +++---- .../publications/PublicationFeed.svelte | 7 ++-- src/lib/components/util/Interactions.svelte | 7 ++-- src/lib/components/util/Profile.svelte | 12 +++--- src/lib/ndk.ts | 27 ++++++++----- src/lib/services/publisher.ts | 10 ++--- src/lib/stores/userStore.ts | 13 ++----- src/lib/utils/event_input_utils.ts | 29 +++----------- src/lib/utils/event_search.ts | 18 ++++----- src/lib/utils/kind24_utils.ts | 26 +++++-------- src/lib/utils/nostrEventService.ts | 8 ++-- src/lib/utils/nostrUtils.ts | 10 ++--- src/lib/utils/profileCache.ts | 14 +++---- src/lib/utils/profile_search.ts | 15 ++++---- src/lib/utils/subscription_search.ts | 28 +++++++++----- src/lib/utils/tag_event_fetch.ts | 11 +++--- src/routes/+layout.svelte | 29 +++----------- src/routes/+layout.ts | 8 ++++ src/routes/contact/+page.svelte | 7 ++-- src/routes/my-notes/+page.svelte | 6 +-- src/routes/new/edit/+page.svelte | 10 +++-- src/routes/publication/+page.server.ts | 2 +- .../[type]/[identifier]/+layout.server.ts | 29 +------------- .../publication/[type]/[identifier]/+page.ts | 17 +-------- src/routes/visualize/+page.svelte | 21 +++++----- src/styles/events.css | 5 --- tests/unit/eventInput30040.test.ts | 9 ++--- tests/unit/tagExpansion.test.ts | 38 +++++++++---------- 38 files changed, 216 insertions(+), 321 deletions(-) create mode 100644 src/routes/+layout.ts delete mode 100644 src/styles/events.css diff --git a/src/app.css b/src/app.css index 31b91cf..e369c72 100644 --- a/src/app.css +++ b/src/app.css @@ -2,7 +2,6 @@ @import "./styles/scrollbar.css"; @import "./styles/publications.css"; @import "./styles/visualize.css"; -@import "./styles/events.css"; @import "./styles/asciidoc.css"; /* Custom styles */ @@ -321,6 +320,10 @@ } @layer components { + canvas.qr-code { + @apply block mx-auto my-4; + } + /* Legend */ .leather-legend { @apply relative m-4 sm:m-0 sm:absolute sm:top-1 sm:left-1 flex-shrink-0 p-2 diff --git a/src/lib/components/CommentViewer.svelte b/src/lib/components/CommentViewer.svelte index 1937f80..67cda8a 100644 --- a/src/lib/components/CommentViewer.svelte +++ b/src/lib/components/CommentViewer.svelte @@ -2,7 +2,7 @@ import { Button, P, Heading } from "flowbite-svelte"; import { getUserMetadata, toNpub } from "$lib/utils/nostrUtils"; import { neventEncode } from "$lib/utils"; - import { activeInboxRelays, ndkInstance } from "$lib/ndk"; + import { activeInboxRelays, getNdkContext } from "$lib/ndk"; import { goto } from "$app/navigation"; import { onMount } from "svelte"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; @@ -10,6 +10,8 @@ const { event } = $props<{ event: NDKEvent }>(); + const ndk = getNdkContext(); + // AI-NOTE: 2025-01-08 - Clean, efficient comment viewer implementation // This component fetches and displays threaded comments with proper hierarchy // Uses simple, reliable profile fetching and efficient state management @@ -124,15 +126,15 @@ // Get all available relays for a more comprehensive search // Use the full NDK pool relays instead of just active relays - const ndkPoolRelays = Array.from($ndkInstance.pool.relays.values()).map(relay => relay.url); + const ndkPoolRelays = Array.from(ndk.pool.relays.values()).map(relay => relay.url); console.log(`[CommentViewer] Using ${ndkPoolRelays.length} NDK pool relays for search:`, ndkPoolRelays); // Try all filters to find comments with full relay set - activeSub = $ndkInstance.subscribe(filters); + activeSub = ndk.subscribe(filters); // Also try a direct search for the specific comment we're looking for console.log(`[CommentViewer] Also searching for specific comment: 64173a81c2a8e26342d4a75d3def804da8644377bde99cfdfeaf189dff87f942`); - const specificCommentSub = $ndkInstance.subscribe({ + const specificCommentSub = ndk.subscribe({ ids: ["64173a81c2a8e26342d4a75d3def804da8644377bde99cfdfeaf189dff87f942"] }); @@ -291,7 +293,7 @@ try { // Try a broader search to see if there are any events that might be comments - const testSub = $ndkInstance.subscribe({ + const testSub = ndk.subscribe({ kinds: [1, 1111, 9802], "#e": [event.id], limit: 10, @@ -462,7 +464,7 @@ console.log(`[CommentViewer] Fetching nested replies for event:`, eventId); // Search for replies to this specific event - const nestedSub = $ndkInstance.subscribe({ + const nestedSub = ndk.subscribe({ kinds: [1, 1111, 9802], "#e": [eventId], limit: 50, @@ -506,7 +508,7 @@ if (dTag) { const eventAddress = `${event.kind}:${event.pubkey}:${dTag}`; - const nip22Sub = $ndkInstance.subscribe({ + const nip22Sub = ndk.subscribe({ kinds: [1111, 9802], "#a": [eventAddress], limit: 50, diff --git a/src/lib/components/EventInput.svelte b/src/lib/components/EventInput.svelte index cec2cde..b15140c 100644 --- a/src/lib/components/EventInput.svelte +++ b/src/lib/components/EventInput.svelte @@ -13,24 +13,24 @@ get30040FixGuidance, } from "$lib/utils/event_input_utils"; import { - extractDocumentMetadata, extractSmartMetadata, metadataToTags, removeMetadataFromContent } from "$lib/utils/asciidoc_metadata"; import { get } from "svelte/store"; - import { ndkInstance } from "$lib/ndk"; import { userPubkey } from "$lib/stores/authStore.Svelte"; import { userStore } from "$lib/stores/userStore"; - import { NDKEvent as NDKEventClass } from "@nostr-dev-kit/ndk"; + import NDK, { NDKEvent as NDKEventClass } from "@nostr-dev-kit/ndk"; import type { NDKEvent } from "$lib/utils/nostrUtils"; import { prefixNostrAddresses } from "$lib/utils/nostrUtils"; - import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk"; + import { activeInboxRelays, activeOutboxRelays, getNdkContext } from "$lib/ndk"; import { Button } from "flowbite-svelte"; import { goto } from "$app/navigation"; import { WebSocketPool } from "$lib/data_structures/websocket_pool"; import { anonymousRelays } from "$lib/consts"; + const ndk = getNdkContext(); + let kind = $state(30040); let tags = $state<[string, string][]>([]); let content = $state(""); @@ -221,7 +221,6 @@ createdAt = Math.floor(Date.now() / 1000); try { - const ndk = get(ndkInstance); const currentUserPubkey = get(userPubkey as any); const userState = get(userStore); diff --git a/src/lib/components/EventSearch.svelte b/src/lib/components/EventSearch.svelte index 5f63049..19f4ac3 100644 --- a/src/lib/components/EventSearch.svelte +++ b/src/lib/components/EventSearch.svelte @@ -1,5 +1,4 @@
diff --git a/src/lib/components/RelayDisplay.svelte b/src/lib/components/RelayDisplay.svelte index 02ff24b..941e697 100644 --- a/src/lib/components/RelayDisplay.svelte +++ b/src/lib/components/RelayDisplay.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/publication/+page.server.ts b/src/routes/publication/+page.server.ts index f001a1c..0be4172 100644 --- a/src/routes/publication/+page.server.ts +++ b/src/routes/publication/+page.server.ts @@ -17,7 +17,7 @@ const IDENTIFIER_PREFIXES = { NEVENT: "nevent", } as const; -export const load: PageServerLoad = ({ url }) => { +export const load: PageServerLoad = ({ url }: { url: URL }) => { const id = url.searchParams.get("id"); const dTag = url.searchParams.get("d"); diff --git a/src/routes/publication/[type]/[identifier]/+layout.server.ts b/src/routes/publication/[type]/[identifier]/+layout.server.ts index 72e4d67..4670248 100644 --- a/src/routes/publication/[type]/[identifier]/+layout.server.ts +++ b/src/routes/publication/[type]/[identifier]/+layout.server.ts @@ -1,38 +1,11 @@ -import { error } from "@sveltejs/kit"; import type { LayoutServerLoad } from "./$types"; -import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; -// AI-NOTE: Server-side event fetching for SEO metadata -async function fetchEventServerSide( - type: string, - identifier: string, -): Promise { - // For now, return null to indicate server-side fetch not implemented - // This will fall back to client-side fetching - return null; -} -export const load: LayoutServerLoad = async ({ params, url }) => { - const { type, identifier } = params; - - // Try to fetch event server-side for metadata - const indexEvent = await fetchEventServerSide(type, identifier); - - // Extract metadata for meta tags (use fallbacks if no event found) - const title = indexEvent?.tags.find((tag) => tag[0] === "title")?.[1] || - "Alexandria Publication"; - const summary = indexEvent?.tags.find((tag) => tag[0] === "summary")?.[1] || - "Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages."; - const image = indexEvent?.tags.find((tag) => tag[0] === "image")?.[1] || - "/screenshots/old_books.jpg"; +export const load: LayoutServerLoad = ({ url }: { url: URL }) => { const currentUrl = `${url.origin}${url.pathname}`; return { - indexEvent, // Will be null, triggering client-side fetch metadata: { - title, - summary, - image, currentUrl, }, }; diff --git a/src/routes/publication/[type]/[identifier]/+page.ts b/src/routes/publication/[type]/[identifier]/+page.ts index bc43ef0..69d8a59 100644 --- a/src/routes/publication/[type]/[identifier]/+page.ts +++ b/src/routes/publication/[type]/[identifier]/+page.ts @@ -9,16 +9,12 @@ import { import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; export const load: PageLoad = async ( - { params, parent }: { + { params }: { params: { type: string; identifier: string }; - parent: any; }, ) => { const { type, identifier } = params; - // Get layout data (no server-side data since SSR is disabled) - const layoutData = await parent(); - // AI-NOTE: Always fetch client-side since server-side fetch returns null for now let indexEvent: NostrEvent | null = null; @@ -74,20 +70,9 @@ export const load: PageLoad = async ( const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? ""; - // AI-NOTE: Use proper NDK instance from layout or create one with relays - let ndk = layoutData?.ndk; - if (!ndk) { - // Import NDK dynamically to avoid SSR issues - const NDK = (await import("@nostr-dev-kit/ndk")).default; - // Import initNdk to get properly configured NDK with relays - const { initNdk } = await import("$lib/ndk"); - ndk = initNdk(); - } - const result = { publicationType, indexEvent, - ndk, // Use minimal NDK instance }; return result; diff --git a/src/routes/visualize/+page.svelte b/src/routes/visualize/+page.svelte index 91925ec..7d8124a 100644 --- a/src/routes/visualize/+page.svelte +++ b/src/routes/visualize/+page.svelte @@ -8,7 +8,6 @@ import { onMount } from "svelte"; import { get } from "svelte/store"; import EventNetwork from "$lib/navigator/EventNetwork/index.svelte"; - import { ndkInstance } from "$lib/ndk"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { filterValidIndexEvents } from "$lib/utils"; import { networkFetchLimit } from "$lib/state"; @@ -17,7 +16,7 @@ import type { PageData } from './$types'; import { getEventKindColor, getEventKindName } from "$lib/utils/eventColors"; import { extractPubkeysFromEvents, batchFetchProfiles } from "$lib/utils/profileCache"; - import { activePubkey } from "$lib/ndk"; + import { activePubkey, getNdkContext } from "$lib/ndk"; // Import utility functions for tag-based event fetching // These functions handle the complex logic of finding publications by tags // and extracting their associated content events @@ -28,6 +27,8 @@ } from "$lib/utils/tag_event_fetch"; import { deduplicateAndCombineEvents } from "$lib/utils/eventDeduplication"; import type { EventCounts } from "$lib/types"; + + const ndk = getNdkContext(); // Configuration const DEBUG = true; // Set to true to enable debug logging @@ -130,7 +131,7 @@ // If limit is 1, only fetch the current user's follow list if (config.limit === 1) { - const userFollowList = await $ndkInstance.fetchEvents({ + const userFollowList = await ndk.fetchEvents({ kinds: [3], authors: [currentUserPubkey], limit: 1 @@ -148,7 +149,7 @@ debug(`Fetched user's follow list`); } else { // If limit > 1, fetch the user's follow list plus additional ones from people they follow - const userFollowList = await $ndkInstance.fetchEvents({ + const userFollowList = await ndk.fetchEvents({ kinds: [3], authors: [currentUserPubkey], limit: 1 @@ -180,7 +181,7 @@ debug(`Fetching ${pubkeysToFetch.length} additional follow lists (total limit: ${config.limit})`); - const additionalFollowLists = await $ndkInstance.fetchEvents({ + const additionalFollowLists = await ndk.fetchEvents({ kinds: [3], authors: pubkeysToFetch }); @@ -215,7 +216,7 @@ debug(`Fetching level ${level} follow lists for ${currentLevelPubkeys.length} pubkeys`); // Fetch follow lists for this level - const levelFollowLists = await $ndkInstance.fetchEvents({ + const levelFollowLists = await ndk.fetchEvents({ kinds: [3], authors: currentLevelPubkeys }); @@ -362,7 +363,7 @@ const followEvents = await fetchFollowLists(config); allFetchedEvents.push(...followEvents); } else { - const fetchedEvents = await $ndkInstance.fetchEvents( + const fetchedEvents = await ndk.fetchEvents( { kinds: [config.kind], limit: config.limit @@ -394,7 +395,7 @@ if (data.eventId) { // Fetch specific publication debug(`Fetching specific publication: ${data.eventId}`); - const event = await $ndkInstance.fetchEvent(data.eventId); + const event = await ndk.fetchEvent(data.eventId); if (!event) { throw new Error(`Publication not found: ${data.eventId}`); @@ -414,7 +415,7 @@ const indexConfig = publicationConfigs.find(ec => ec.kind === INDEX_EVENT_KIND); const indexLimit = indexConfig?.limit || 20; - const indexEvents = await $ndkInstance.fetchEvents( + const indexEvents = await ndk.fetchEvents( { kinds: [INDEX_EVENT_KIND], limit: indexLimit @@ -455,7 +456,7 @@ const contentEventPromises = Array.from(referencesByAuthor.entries()).map( async ([author, refs]) => { const dTags = [...new Set(refs.map(r => r.dTag))]; // Dedupe d-tags - return $ndkInstance.fetchEvents({ + return ndk.fetchEvents({ kinds: enabledContentKinds, // Only fetch enabled kinds authors: [author], "#d": dTags, diff --git a/src/styles/events.css b/src/styles/events.css deleted file mode 100644 index 3c61536..0000000 --- a/src/styles/events.css +++ /dev/null @@ -1,5 +0,0 @@ -@layer components { - canvas.qr-code { - @apply block mx-auto my-4; - } -} diff --git a/tests/unit/eventInput30040.test.ts b/tests/unit/eventInput30040.test.ts index 9fa185c..a7064c3 100644 --- a/tests/unit/eventInput30040.test.ts +++ b/tests/unit/eventInput30040.test.ts @@ -3,10 +3,6 @@ import { build30040EventSet, validate30040EventSet, } from "../../src/lib/utils/event_input_utils"; -import { - extractDocumentMetadata, - parseAsciiDocWithMetadata, -} from "../../src/lib/utils/asciidoc_metadata"; // Mock NDK and other dependencies vi.mock("@nostr-dev-kit/ndk", () => ({ @@ -22,6 +18,7 @@ vi.mock("@nostr-dev-kit/ndk", () => ({ })), })); +// TODO: Replace with getNdkContext mock. vi.mock("../../src/lib/ndk", () => ({ ndkInstance: { subscribe: vi.fn(), @@ -265,7 +262,7 @@ This is the preamble content. expect(sectionEvents).toHaveLength(3); // All sections should have empty content - sectionEvents.forEach((section, index) => { + sectionEvents.forEach((section: any, index: number) => { expect(section.kind).toBe(30041); expect(section.content).toBe(""); expect(section.tags).toContainEqual([ @@ -320,7 +317,7 @@ This is the preamble content. expect(sectionEvents).toHaveLength(3); // All sections should have empty content - sectionEvents.forEach((section, index) => { + sectionEvents.forEach((section: any, index: number) => { expect(section.kind).toBe(30041); expect(section.content).toBe(""); expect(section.tags).toContainEqual([ diff --git a/tests/unit/tagExpansion.test.ts b/tests/unit/tagExpansion.test.ts index 5de5f94..307ebd9 100644 --- a/tests/unit/tagExpansion.test.ts +++ b/tests/unit/tagExpansion.test.ts @@ -4,7 +4,6 @@ import { fetchProfilesForNewEvents, fetchTaggedEventsFromRelays, findTaggedEventsInFetched, - type TagExpansionResult, } from "../../src/lib/utils/tag_event_fetch"; // Mock NDKEvent for testing @@ -48,6 +47,7 @@ const mockNDK = { }; // Mock the ndkInstance store +// TODO: Replace with getNdkContext mock. vi.mock("../../src/lib/ndk", () => ({ ndkInstance: { subscribe: vi.fn((fn) => { @@ -179,8 +179,8 @@ describe("Tag Expansion Tests", () => { // Should return the matching publications expect(result.publications).toHaveLength(2); - expect(result.publications.map((p) => p.id)).toContain("pub1"); - expect(result.publications.map((p) => p.id)).toContain("pub2"); + expect(result.publications.map((p: any) => p.id)).toContain("pub1"); + expect(result.publications.map((p: any) => p.id)).toContain("pub2"); // Should fetch content events for the publications expect(mockNDK.fetchEvents).toHaveBeenCalledWith({ @@ -210,9 +210,9 @@ describe("Tag Expansion Tests", () => { // Should exclude pub1 since it already exists expect(result.publications).toHaveLength(2); - expect(result.publications.map((p) => p.id)).not.toContain("pub1"); - expect(result.publications.map((p) => p.id)).toContain("pub2"); - expect(result.publications.map((p) => p.id)).toContain("pub3"); + expect(result.publications.map((p: any) => p.id)).not.toContain("pub1"); + expect(result.publications.map((p: any) => p.id)).toContain("pub2"); + expect(result.publications.map((p: any) => p.id)).toContain("pub3"); }); it("should handle empty tag array gracefully", async () => { @@ -251,15 +251,15 @@ describe("Tag Expansion Tests", () => { // Should find publications with bitcoin tag expect(result.publications).toHaveLength(2); - expect(result.publications.map((p) => p.id)).toContain("pub1"); - expect(result.publications.map((p) => p.id)).toContain("pub2"); + expect(result.publications.map((p: any) => p.id)).toContain("pub1"); + expect(result.publications.map((p: any) => p.id)).toContain("pub2"); // Should find content events for those publications expect(result.contentEvents).toHaveLength(4); - expect(result.contentEvents.map((c) => c.id)).toContain("content1"); - expect(result.contentEvents.map((c) => c.id)).toContain("content2"); - expect(result.contentEvents.map((c) => c.id)).toContain("content3"); - expect(result.contentEvents.map((c) => c.id)).toContain("content4"); + expect(result.contentEvents.map((c: any) => c.id)).toContain("content1"); + expect(result.contentEvents.map((c: any) => c.id)).toContain("content2"); + expect(result.contentEvents.map((c: any) => c.id)).toContain("content3"); + expect(result.contentEvents.map((c: any) => c.id)).toContain("content4"); }); it("should exclude base events from search results", () => { @@ -277,8 +277,8 @@ describe("Tag Expansion Tests", () => { // Should exclude pub1 since it's a base event expect(result.publications).toHaveLength(1); - expect(result.publications.map((p) => p.id)).not.toContain("pub1"); - expect(result.publications.map((p) => p.id)).toContain("pub2"); + expect(result.publications.map((p: any) => p.id)).not.toContain("pub1"); + expect(result.publications.map((p: any) => p.id)).toContain("pub2"); }); it("should handle multiple tags (OR logic)", () => { @@ -296,9 +296,9 @@ describe("Tag Expansion Tests", () => { // Should find publications with either bitcoin OR ethereum tags expect(result.publications).toHaveLength(3); - expect(result.publications.map((p) => p.id)).toContain("pub1"); // bitcoin - expect(result.publications.map((p) => p.id)).toContain("pub2"); // bitcoin - expect(result.publications.map((p) => p.id)).toContain("pub3"); // ethereum + expect(result.publications.map((p: any) => p.id)).toContain("pub1"); // bitcoin + expect(result.publications.map((p: any) => p.id)).toContain("pub2"); // bitcoin + expect(result.publications.map((p: any) => p.id)).toContain("pub3"); // ethereum }); it("should handle events without tags gracefully", () => { @@ -324,7 +324,7 @@ describe("Tag Expansion Tests", () => { ); // Should not include events without tags - expect(result.publications.map((p) => p.id)).not.toContain("no-tags"); + expect(result.publications.map((p: any) => p.id)).not.toContain("no-tags"); }); }); @@ -497,7 +497,7 @@ describe("Tag Expansion Tests", () => { // Should handle d-tags with colons correctly expect(result.publications).toHaveLength(3); - expect(result.contentEvents.map((c) => c.id)).toContain("colon-content"); + expect(result.contentEvents.map((c: any) => c.id)).toContain("colon-content"); }); }); });