Browse Source

fix relays and subscription search

master
silberengel 7 months ago
parent
commit
577d8c832a
  1. 22
      src/lib/components/CommentViewer.svelte
  2. 88
      src/lib/ndk.ts
  3. 213
      src/lib/utils/subscription_search.ts
  4. 24
      src/routes/+layout.svelte
  5. 27
      src/routes/events/+page.svelte

22
src/lib/components/CommentViewer.svelte

@ -294,12 +294,6 @@
let parsedContent = await parseBasicmarkup(content); let parsedContent = await parseBasicmarkup(content);
// Make images blurry until clicked
parsedContent = parsedContent.replace(
/<img([^>]+)>/g,
'<img$1 class="blur-sm hover:blur-none transition-all duration-300 cursor-pointer" onclick="this.classList.toggle(\'blur-sm\')" style="filter: blur(4px);" onload="this.style.filter=\'blur(4px)\'" onerror="(e) => (e.target as HTMLImageElement).style.display = \'none\'">'
);
return parsedContent; return parsedContent;
} }
</script> </script>
@ -313,24 +307,32 @@
> >
<div class="flex justify-between items-start mb-2"> <div class="flex justify-between items-start mb-2">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button
class="cursor-pointer"
onclick={() => goto(`/events?n=${toNpub(node.event.pubkey)}`)}
>
{#if getAuthorPicture(node.event.pubkey)} {#if getAuthorPicture(node.event.pubkey)}
<img <img
src={getAuthorPicture(node.event.pubkey)} src={getAuthorPicture(node.event.pubkey)}
alt={getAuthorName(node.event.pubkey)} alt={getAuthorName(node.event.pubkey)}
class="w-8 h-8 rounded-full object-cover" class="w-8 h-8 rounded-full object-cover hover:opacity-80 transition-opacity"
onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'} onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'}
/> />
{:else} {:else}
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center"> <div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center hover:opacity-80 transition-opacity">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300"> <span class="text-sm font-medium text-gray-600 dark:text-gray-300">
{getAuthorName(node.event.pubkey).charAt(0).toUpperCase()} {getAuthorName(node.event.pubkey).charAt(0).toUpperCase()}
</span> </span>
</div> </div>
{/if} {/if}
</button>
<div class="flex flex-col min-w-0"> <div class="flex flex-col min-w-0">
<span class="font-medium text-gray-900 dark:text-white truncate"> <button
class="font-medium text-gray-900 dark:text-white truncate hover:underline cursor-pointer text-left"
onclick={() => goto(`/events?n=${toNpub(node.event.pubkey)}`)}
>
{getAuthorName(node.event.pubkey)} {getAuthorName(node.event.pubkey)}
</span> </button>
<span <span
class="text-sm text-gray-500 cursor-help" class="text-sm text-gray-500 cursor-help"
title={formatDate(node.event.created_at || 0)} title={formatDate(node.event.created_at || 0)}

88
src/lib/ndk.ts

@ -33,6 +33,63 @@ export const outboxRelays = writable<string[]>([]);
export const activeInboxRelays = writable<string[]>([]); export const activeInboxRelays = writable<string[]>([]);
export const activeOutboxRelays = writable<string[]>([]); export const activeOutboxRelays = writable<string[]>([]);
// AI-NOTE: 2025-01-08 - Persistent relay storage to avoid recalculation
let persistentRelaySet: { inboxRelays: string[]; outboxRelays: string[] } | null = null;
let relaySetLastUpdated: number = 0;
const RELAY_SET_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const RELAY_SET_STORAGE_KEY = 'alexandria/relay_set_cache';
/**
* Load persistent relay set from localStorage
*/
function loadPersistentRelaySet(): { relaySet: { inboxRelays: string[]; outboxRelays: string[] } | null; lastUpdated: number } {
try {
const stored = localStorage.getItem(RELAY_SET_STORAGE_KEY);
if (!stored) return { relaySet: null, lastUpdated: 0 };
const data = JSON.parse(stored);
const now = Date.now();
// Check if cache is expired
if (now - data.timestamp > RELAY_SET_CACHE_DURATION) {
localStorage.removeItem(RELAY_SET_STORAGE_KEY);
return { relaySet: null, lastUpdated: 0 };
}
return { relaySet: data.relaySet, lastUpdated: data.timestamp };
} catch (error) {
console.warn('[NDK.ts] Failed to load persistent relay set:', error);
localStorage.removeItem(RELAY_SET_STORAGE_KEY);
return { relaySet: null, lastUpdated: 0 };
}
}
/**
* Save persistent relay set to localStorage
*/
function savePersistentRelaySet(relaySet: { inboxRelays: string[]; outboxRelays: string[] }): void {
try {
const data = {
relaySet,
timestamp: Date.now()
};
localStorage.setItem(RELAY_SET_STORAGE_KEY, JSON.stringify(data));
} catch (error) {
console.warn('[NDK.ts] Failed to save persistent relay set:', error);
}
}
/**
* Clear persistent relay set from localStorage
*/
function clearPersistentRelaySet(): void {
try {
localStorage.removeItem(RELAY_SET_STORAGE_KEY);
} catch (error) {
console.warn('[NDK.ts] Failed to clear persistent relay set:', error);
}
}
// Subscribe to userStore changes and update ndkSignedIn accordingly // Subscribe to userStore changes and update ndkSignedIn accordingly
userStore.subscribe((userState) => { userStore.subscribe((userState) => {
ndkSignedIn.set(userState.signedIn); ndkSignedIn.set(userState.signedIn);
@ -351,15 +408,39 @@ export async function getActiveRelaySet(ndk: NDK): Promise<{ inboxRelays: string
/** /**
* Updates the active relay stores and NDK pool with new relay URLs * Updates the active relay stores and NDK pool with new relay URLs
* @param ndk NDK instance * @param ndk NDK instance
* @param forceUpdate Force update even if cached (default: false)
*/ */
export async function updateActiveRelayStores(ndk: NDK): Promise<void> { export async function updateActiveRelayStores(ndk: NDK, forceUpdate: boolean = false): Promise<void> {
try { try {
// AI-NOTE: 2025-01-08 - Use persistent relay set to avoid recalculation
const now = Date.now();
const cacheExpired = now - relaySetLastUpdated > RELAY_SET_CACHE_DURATION;
// Load from persistent storage if not already loaded
if (!persistentRelaySet) {
const loaded = loadPersistentRelaySet();
persistentRelaySet = loaded.relaySet;
relaySetLastUpdated = loaded.lastUpdated;
}
if (!forceUpdate && persistentRelaySet && !cacheExpired) {
console.debug('[NDK.ts] updateActiveRelayStores: Using cached relay set');
activeInboxRelays.set(persistentRelaySet.inboxRelays);
activeOutboxRelays.set(persistentRelaySet.outboxRelays);
return;
}
console.debug('[NDK.ts] updateActiveRelayStores: Starting relay store update'); console.debug('[NDK.ts] updateActiveRelayStores: Starting relay store update');
// Get the active relay set from the relay management system // Get the active relay set from the relay management system
const relaySet = await getActiveRelaySet(ndk); const relaySet = await getActiveRelaySet(ndk);
console.debug('[NDK.ts] updateActiveRelayStores: Got relay set:', relaySet); console.debug('[NDK.ts] updateActiveRelayStores: Got relay set:', relaySet);
// Cache the relay set
persistentRelaySet = relaySet;
relaySetLastUpdated = now;
savePersistentRelaySet(relaySet); // Save to persistent storage
// Update the stores with the new relay configuration // Update the stores with the new relay configuration
activeInboxRelays.set(relaySet.inboxRelays); activeInboxRelays.set(relaySet.inboxRelays);
activeOutboxRelays.set(relaySet.outboxRelays); activeOutboxRelays.set(relaySet.outboxRelays);
@ -560,6 +641,11 @@ export function logout(user: NDKUser): void {
activeInboxRelays.set([]); activeInboxRelays.set([]);
activeOutboxRelays.set([]); activeOutboxRelays.set([]);
// AI-NOTE: 2025-01-08 - Clear persistent relay set on logout
persistentRelaySet = null;
relaySetLastUpdated = 0;
clearPersistentRelaySet(); // Clear persistent storage
// Stop network monitoring // Stop network monitoring
stopNetworkStatusMonitoring(); stopNetworkStatusMonitoring();

213
src/lib/utils/subscription_search.ts

@ -26,6 +26,17 @@ const normalizeUrl = (url: string): string => {
return url.replace(/\/$/, ''); // Remove trailing slash return url.replace(/\/$/, ''); // Remove trailing slash
}; };
/**
* Filter out unwanted events from search results
* @param events Array of NDKEvent to filter
* @returns Filtered array of NDKEvent
*/
function filterUnwantedEvents(events: NDKEvent[]): NDKEvent[] {
return events.filter(
(event) => !isEmojiReaction(event) && event.kind !== 3 && event.kind !== 5,
);
}
/** /**
* Search for events by subscription type (d, t, n) * Search for events by subscription type (d, t, n)
*/ */
@ -35,6 +46,7 @@ export async function searchBySubscription(
callbacks?: SearchCallbacks, callbacks?: SearchCallbacks,
abortSignal?: AbortSignal, abortSignal?: AbortSignal,
): Promise<SearchResult> { ): Promise<SearchResult> {
const startTime = Date.now(); // AI-NOTE: 2025-01-08 - Track search performance
const normalizedSearchTerm = searchTerm.toLowerCase().trim(); const normalizedSearchTerm = searchTerm.toLowerCase().trim();
console.log("subscription_search: Starting search:", { console.log("subscription_search: Starting search:", {
@ -47,8 +59,23 @@ export async function searchBySubscription(
const cachedResult = searchCache.get(searchType, normalizedSearchTerm); const cachedResult = searchCache.get(searchType, normalizedSearchTerm);
if (cachedResult) { if (cachedResult) {
console.log("subscription_search: Found cached result:", cachedResult); console.log("subscription_search: Found cached result:", cachedResult);
// AI-NOTE: 2025-01-08 - For profile searches, clear cache if it's empty to force fresh search
if (searchType === "n" && cachedResult.events.length === 0) {
console.log("subscription_search: Clearing empty cached profile result to force fresh search");
searchCache.clear(); // Clear all cache to force fresh search
} else if (searchType === "n" && cachedResult.events.length > 0 && cachedResult.secondOrder.length === 0) {
// AI-NOTE: 2025-01-08 - Clear cache if we have profile results but no second-order events
// This forces a fresh search that includes second-order searching
console.log("subscription_search: Clearing cached profile result with no second-order events to force fresh search");
searchCache.clear();
} else if (searchType === "n") {
// AI-NOTE: 2025-01-08 - For profile searches, always clear cache to ensure fresh second-order search
console.log("subscription_search: Clearing cache for profile search to ensure fresh second-order search");
searchCache.clear();
} else {
return cachedResult; return cachedResult;
} }
}
const ndk = get(ndkInstance); const ndk = get(ndkInstance);
if (!ndk) { if (!ndk) {
@ -64,7 +91,7 @@ export async function searchBySubscription(
searchState.timeoutId = setTimeout(() => { searchState.timeoutId = setTimeout(() => {
console.log("subscription_search: Search timeout reached"); console.log("subscription_search: Search timeout reached");
cleanup(); cleanup();
}, TIMEOUTS.SUBSCRIPTION_SEARCH); }, searchType === "n" ? 5000 : TIMEOUTS.SUBSCRIPTION_SEARCH); // AI-NOTE: 2025-01-08 - Shorter timeout for profile searches
// Check for abort signal // Check for abort signal
if (abortSignal?.aborted) { if (abortSignal?.aborted) {
@ -125,7 +152,26 @@ export async function searchBySubscription(
); );
searchCache.set(searchType, normalizedSearchTerm, immediateResult); searchCache.set(searchType, normalizedSearchTerm, immediateResult);
// Start Phase 2 in background for additional results // AI-NOTE: 2025-01-08 - For profile searches, return immediately when found
// but still start background search for second-order results
if (searchType === "n") {
console.log("subscription_search: Profile found, returning immediately but starting background second-order search");
// Start Phase 2 in background for second-order results
searchOtherRelaysInBackground(
searchType,
searchFilter,
searchState,
callbacks,
cleanup,
);
const elapsed = Date.now() - startTime;
console.log(`subscription_search: Profile search completed in ${elapsed}ms`);
return immediateResult;
}
// Start Phase 2 in background for additional results (only for non-profile searches)
searchOtherRelaysInBackground( searchOtherRelaysInBackground(
searchType, searchType,
searchFilter, searchFilter,
@ -135,11 +181,75 @@ export async function searchBySubscription(
); );
return immediateResult; return immediateResult;
} else {
console.log(
"subscription_search: No results from primary relay",
);
// AI-NOTE: 2025-01-08 - For profile searches, if no results found in search relays,
// try all relays as fallback
if (searchType === "n") {
console.log(
"subscription_search: No profile found in search relays, trying all relays",
);
// Try with all relays as fallback
const allRelaySet = new NDKRelaySet(new Set(Array.from(ndk.pool.relays.values())) as any, ndk);
try {
const fallbackEvents = await ndk.fetchEvents(
searchFilter.filter,
{ closeOnEose: true },
allRelaySet,
);
console.log(
"subscription_search: Fallback search returned",
fallbackEvents.size,
"events",
);
processPrimaryRelayResults(
fallbackEvents,
searchType,
searchFilter.subscriptionType,
normalizedSearchTerm,
searchState,
abortSignal,
cleanup,
);
if (hasResults(searchState, searchType)) {
console.log(
"subscription_search: Found profile in fallback search, returning immediately",
);
const fallbackResult = createSearchResult(
searchState,
searchType,
normalizedSearchTerm,
);
searchCache.set(searchType, normalizedSearchTerm, fallbackResult);
const elapsed = Date.now() - startTime;
console.log(`subscription_search: Profile search completed in ${elapsed}ms (fallback)`);
return fallbackResult;
}
} catch (fallbackError) {
console.error("subscription_search: Fallback search failed:", fallbackError);
}
console.log(
"subscription_search: Profile not found in any relays, returning empty result",
);
const emptyResult = createEmptySearchResult(searchType, normalizedSearchTerm);
// AI-NOTE: 2025-01-08 - Don't cache empty profile results as they may be due to search issues
// rather than the profile not existing
const elapsed = Date.now() - startTime;
console.log(`subscription_search: Profile search completed in ${elapsed}ms (not found)`);
return emptyResult;
} else { } else {
console.log( console.log(
"subscription_search: No results from primary relay, continuing to Phase 2", "subscription_search: No results from primary relay, continuing to Phase 2",
); );
} }
}
} catch (error) { } catch (error) {
console.error( console.error(
`subscription_search: Error searching primary relay:`, `subscription_search: Error searching primary relay:`,
@ -153,13 +263,21 @@ export async function searchBySubscription(
} }
// Always do Phase 2: Search all other relays in parallel // Always do Phase 2: Search all other relays in parallel
return searchOtherRelaysInBackground( const result = await searchOtherRelaysInBackground(
searchType, searchType,
searchFilter, searchFilter,
searchState, searchState,
callbacks, callbacks,
cleanup, cleanup,
); );
// AI-NOTE: 2025-01-08 - Log performance for non-profile searches
if (searchType !== "n") {
const elapsed = Date.now() - startTime;
console.log(`subscription_search: ${searchType} search completed in ${elapsed}ms`);
}
return result;
} }
/** /**
@ -253,7 +371,7 @@ async function createProfileSearchFilter(
filter: { filter: {
kinds: [0], kinds: [0],
authors: [decoded.data], authors: [decoded.data],
limit: SEARCH_LIMITS.SPECIFIC_PROFILE, limit: 1, // AI-NOTE: 2025-01-08 - Only need 1 result for specific npub search
}, },
subscriptionType: "npub-specific", subscriptionType: "npub-specific",
}; };
@ -273,7 +391,7 @@ async function createProfileSearchFilter(
filter: { filter: {
kinds: [0], kinds: [0],
authors: [npub], authors: [npub],
limit: SEARCH_LIMITS.SPECIFIC_PROFILE, limit: 1, // AI-NOTE: 2025-01-08 - Only need 1 result for specific npub search
}, },
subscriptionType: "nip05-found", subscriptionType: "nip05-found",
}; };
@ -299,31 +417,38 @@ function createPrimaryRelaySet(
searchType: SearchSubscriptionType, searchType: SearchSubscriptionType,
ndk: any, ndk: any,
): NDKRelaySet { ): NDKRelaySet {
// Use the new relay management system
const searchRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
console.debug('subscription_search: Active relay stores:', {
inboxRelays: get(activeInboxRelays),
outboxRelays: get(activeOutboxRelays),
searchRelays
});
// Debug: Log all relays in NDK pool // Debug: Log all relays in NDK pool
const poolRelays = Array.from(ndk.pool.relays.values()); const poolRelays = Array.from(ndk.pool.relays.values());
console.debug('subscription_search: NDK pool relays:', poolRelays.map((r: any) => r.url)); console.debug('subscription_search: NDK pool relays:', poolRelays.map((r: any) => r.url));
if (searchType === "n") { if (searchType === "n") {
// For profile searches, use search relays first // AI-NOTE: 2025-01-08 - For profile searches, prioritize search relays for speed
const profileRelaySet = poolRelays.filter( // Use search relays first, then fall back to all relays if needed
const searchRelaySet = poolRelays.filter(
(relay: any) => (relay: any) =>
searchRelays.some( searchRelays.some(
(searchRelay: string) => (searchRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(searchRelay), normalizeUrl(relay.url) === normalizeUrl(searchRelay),
), ),
); );
console.debug('subscription_search: Profile relay set:', profileRelaySet.map((r: any) => r.url));
return new NDKRelaySet(new Set(profileRelaySet) as any, ndk); if (searchRelaySet.length > 0) {
console.debug('subscription_search: Profile search - using search relays for speed:', searchRelaySet.map((r: any) => r.url));
return new NDKRelaySet(new Set(searchRelaySet) as any, ndk);
} else {
// Fallback to all relays if search relays not available
console.debug('subscription_search: Profile search - fallback to all relays:', poolRelays.map((r: any) => r.url));
return new NDKRelaySet(new Set(poolRelays) as any, ndk);
}
} else { } else {
// For other searches, use active relays first // For other searches, use active relays first
const searchRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
console.debug('subscription_search: Active relay stores:', {
inboxRelays: get(activeInboxRelays),
outboxRelays: get(activeOutboxRelays),
searchRelays
});
const activeRelaySet = poolRelays.filter( const activeRelaySet = poolRelays.filter(
(relay: any) => (relay: any) =>
searchRelays.some( searchRelays.some(
@ -534,11 +659,9 @@ function searchOtherRelaysInBackground(
new Set( new Set(
Array.from(ndk.pool.relays.values()).filter((relay: any) => { Array.from(ndk.pool.relays.values()).filter((relay: any) => {
if (searchType === "n") { if (searchType === "n") {
// For profile searches, exclude search relays from fallback search // AI-NOTE: 2025-01-08 - For profile searches, use ALL available relays
return !searchRelays.some( // Don't exclude any relays since we want maximum coverage
(searchRelay: string) => return true;
normalizeUrl(relay.url) === normalizeUrl(searchRelay),
);
} else { } else {
// For other searches, exclude community relays from fallback search // For other searches, exclude community relays from fallback search
return !communityRelays.some( return !communityRelays.some(
@ -652,6 +775,7 @@ function processProfileEoseResults(
) { ) {
const targetPubkey = dedupedProfiles[0]?.pubkey; const targetPubkey = dedupedProfiles[0]?.pubkey;
if (targetPubkey) { if (targetPubkey) {
console.log("subscription_search: Triggering second-order search for npub-specific profile:", targetPubkey);
performSecondOrderSearchInBackground( performSecondOrderSearchInBackground(
"n", "n",
dedupedProfiles, dedupedProfiles,
@ -660,11 +784,14 @@ function processProfileEoseResults(
targetPubkey, targetPubkey,
callbacks, callbacks,
); );
} else {
console.log("subscription_search: No targetPubkey found for second-order search");
} }
} else if (searchFilter.subscriptionType === "profile") { } else if (searchFilter.subscriptionType === "profile") {
// For general profile searches, perform second-order search for each found profile // For general profile searches, perform second-order search for each found profile
for (const profile of dedupedProfiles) { for (const profile of dedupedProfiles) {
if (profile.pubkey) { if (profile.pubkey) {
console.log("subscription_search: Triggering second-order search for general profile:", profile.pubkey);
performSecondOrderSearchInBackground( performSecondOrderSearchInBackground(
"n", "n",
dedupedProfiles, dedupedProfiles,
@ -675,6 +802,8 @@ function processProfileEoseResults(
); );
} }
} }
} else {
console.log("subscription_search: No second-order search triggered for subscription type:", searchFilter.subscriptionType);
} }
return { return {
@ -784,6 +913,7 @@ async function performSecondOrderSearchInBackground(
callbacks?: SearchCallbacks, callbacks?: SearchCallbacks,
) { ) {
try { try {
console.log("subscription_search: Starting second-order search for", searchType, "with targetPubkey:", targetPubkey);
const ndk = get(ndkInstance); const ndk = get(ndkInstance);
let allSecondOrderEvents: NDKEvent[] = []; let allSecondOrderEvents: NDKEvent[] = [];
@ -797,6 +927,8 @@ async function performSecondOrderSearchInBackground(
const searchPromise = (async () => { const searchPromise = (async () => {
if (searchType === "n" && targetPubkey) { if (searchType === "n" && targetPubkey) {
console.log("subscription_search: Searching for events mentioning pubkey:", targetPubkey);
// Search for events that mention this pubkey via p-tags // Search for events that mention this pubkey via p-tags
const pTagFilter = { "#p": [targetPubkey] }; const pTagFilter = { "#p": [targetPubkey] };
const pTagEvents = await ndk.fetchEvents( const pTagEvents = await ndk.fetchEvents(
@ -804,11 +936,25 @@ async function performSecondOrderSearchInBackground(
{ closeOnEose: true }, { closeOnEose: true },
new NDKRelaySet(new Set(Array.from(ndk.pool.relays.values())), ndk), new NDKRelaySet(new Set(Array.from(ndk.pool.relays.values())), ndk),
); );
// Filter out emoji reactions console.log("subscription_search: Found", pTagEvents.size, "events with p-tag for", targetPubkey);
const filteredEvents = Array.from(pTagEvents).filter(
(event) => !isEmojiReaction(event), // AI-NOTE: 2025-01-08 - Also search for events written by this pubkey
const authorFilter = { authors: [targetPubkey] };
const authorEvents = await ndk.fetchEvents(
authorFilter,
{ closeOnEose: true },
new NDKRelaySet(new Set(Array.from(ndk.pool.relays.values())), ndk),
); );
allSecondOrderEvents = [...allSecondOrderEvents, ...filteredEvents]; console.log("subscription_search: Found", authorEvents.size, "events written by", targetPubkey);
// Filter out unwanted events from both sets
const filteredPTagEvents = filterUnwantedEvents(Array.from(pTagEvents));
const filteredAuthorEvents = filterUnwantedEvents(Array.from(authorEvents));
console.log("subscription_search: After filtering unwanted events:", filteredPTagEvents.length, "p-tag events,", filteredAuthorEvents.length, "author events");
// Combine both sets of events
allSecondOrderEvents = [...filteredPTagEvents, ...filteredAuthorEvents];
} else if (searchType === "d") { } else if (searchType === "d") {
// Parallel fetch for #e and #a tag events // Parallel fetch for #e and #a tag events
const relaySet = new NDKRelaySet( const relaySet = new NDKRelaySet(
@ -831,13 +977,9 @@ async function performSecondOrderSearchInBackground(
) )
: Promise.resolve([]), : Promise.resolve([]),
]); ]);
// Filter out emoji reactions // Filter out unwanted events
const filteredETagEvents = Array.from(eTagEvents).filter( const filteredETagEvents = filterUnwantedEvents(Array.from(eTagEvents));
(event) => !isEmojiReaction(event), const filteredATagEvents = filterUnwantedEvents(Array.from(aTagEvents));
);
const filteredATagEvents = Array.from(aTagEvents).filter(
(event) => !isEmojiReaction(event),
);
allSecondOrderEvents = [ allSecondOrderEvents = [
...allSecondOrderEvents, ...allSecondOrderEvents,
...filteredETagEvents, ...filteredETagEvents,
@ -866,6 +1008,8 @@ async function performSecondOrderSearchInBackground(
.sort((a, b) => (b.created_at || 0) - (a.created_at || 0)) .sort((a, b) => (b.created_at || 0) - (a.created_at || 0))
.slice(0, SEARCH_LIMITS.SECOND_ORDER_RESULTS); .slice(0, SEARCH_LIMITS.SECOND_ORDER_RESULTS);
console.log("subscription_search: Second-order search completed with", sortedSecondOrder.length, "results");
// Update the search results with second-order events // Update the search results with second-order events
const result: SearchResult = { const result: SearchResult = {
events: firstOrderEvents, events: firstOrderEvents,
@ -882,7 +1026,10 @@ async function performSecondOrderSearchInBackground(
// Notify UI of updated results // Notify UI of updated results
if (callbacks?.onSecondOrderUpdate) { if (callbacks?.onSecondOrderUpdate) {
console.log("subscription_search: Calling onSecondOrderUpdate callback with", sortedSecondOrder.length, "second-order events");
callbacks.onSecondOrderUpdate(result); callbacks.onSecondOrderUpdate(result);
} else {
console.log("subscription_search: No onSecondOrderUpdate callback available");
} }
})(); })();

24
src/routes/+layout.svelte

@ -5,7 +5,10 @@
import { page } from "$app/stores"; import { page } from "$app/stores";
import { Alert } from "flowbite-svelte"; import { Alert } from "flowbite-svelte";
import { HammerSolid } from "flowbite-svelte-icons"; import { HammerSolid } from "flowbite-svelte-icons";
import { logCurrentRelayConfiguration } from "$lib/ndk"; import { logCurrentRelayConfiguration, activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
// Define children prop for Svelte 5
let { children } = $props();
// Get standard metadata for OpenGraph tags // Get standard metadata for OpenGraph tags
let title = "Library of Alexandria"; let title = "Library of Alexandria";
@ -16,12 +19,23 @@
let summary = let summary =
"Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages."; "Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.";
// Reactive effect to log relay configuration when stores change
$effect(() => {
const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays;
// Only log if we have relays (not empty arrays)
if (inboxRelays.length > 0 || outboxRelays.length > 0) {
console.log('🔌 Relay Configuration Updated:');
console.log('📥 Inbox Relays:', inboxRelays);
console.log('📤 Outbox Relays:', outboxRelays);
console.log(`📊 Total: ${inboxRelays.length} inbox, ${outboxRelays.length} outbox`);
}
});
onMount(() => { onMount(() => {
const rect = document.body.getBoundingClientRect(); const rect = document.body.getBoundingClientRect();
// document.body.style.height = `${rect.height}px`; // document.body.style.height = `${rect.height}px`;
// Log relay configuration when layout mounts
logCurrentRelayConfiguration();
}); });
</script> </script>
@ -47,5 +61,5 @@
<div class={"leather mt-[76px] w-full mx-auto flex flex-col items-center"}> <div class={"leather mt-[76px] w-full mx-auto flex flex-col items-center"}>
<Navigation class="fixed top-0" /> <Navigation class="fixed top-0" />
<slot /> {@render children()}
</div> </div>

27
src/routes/events/+page.svelte

@ -151,21 +151,27 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
searchInProgress = searchInProgress =
loading || (results.length > 0 && secondOrder.length === 0); loading || (results.length > 0 && secondOrder.length === 0);
// Show second-order search message when we have first-order results but no second-order yet // AI-NOTE: 2025-01-08 - Only show second-order search message if we're actually searching
// Don't show it for cached results that have no second-order events
if ( if (
results.length > 0 && results.length > 0 &&
secondOrder.length === 0 && secondOrder.length === 0 &&
searchTypeParam === "n" searchTypeParam === "n" &&
!loading // Only show message if we're actively searching, not for cached results
) { ) {
secondOrderSearchMessage = `Found ${results.length} profile(s). Starting second-order search for events mentioning these profiles...`; secondOrderSearchMessage = `Found ${results.length} profile(s). Starting second-order search for events mentioning these profiles...`;
} else if ( } else if (
results.length > 0 && results.length > 0 &&
secondOrder.length === 0 && secondOrder.length === 0 &&
searchTypeParam === "d" searchTypeParam === "d" &&
!loading // Only show message if we're actively searching, not for cached results
) { ) {
secondOrderSearchMessage = `Found ${results.length} event(s). Starting second-order search for events referencing these events...`; secondOrderSearchMessage = `Found ${results.length} event(s). Starting second-order search for events referencing these events...`;
} else if (secondOrder.length > 0) { } else if (secondOrder.length > 0) {
secondOrderSearchMessage = null; secondOrderSearchMessage = null;
} else {
// Clear message if we have results but no second-order search is happening
secondOrderSearchMessage = null;
} }
// Check community status for all search results // Check community status for all search results
@ -347,9 +353,18 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
// Log relay configuration when page mounts // Reactive effect to log relay configuration when stores change
onMount(() => { $effect(() => {
logCurrentRelayConfiguration(); const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays;
// Only log if we have relays (not empty arrays)
if (inboxRelays.length > 0 || outboxRelays.length > 0) {
console.log('🔌 Events Page - Relay Configuration Updated:');
console.log('📥 Inbox Relays:', inboxRelays);
console.log('📤 Outbox Relays:', outboxRelays);
console.log(`📊 Total: ${inboxRelays.length} inbox, ${outboxRelays.length} outbox`);
}
}); });
</script> </script>

Loading…
Cancel
Save