Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
576d37b
perf: parallelize OR term search with Promise.allSettled
alltheseas Feb 18, 2026
b2f6bbb
perf: parallelize author resolution with Promise.all
alltheseas Feb 18, 2026
d03d990
perf: extract relay ping and scope subscription to target relay
alltheseas Feb 18, 2026
a3e47b8
perf: add incremental pagination to search results
alltheseas Feb 18, 2026
b7482c6
perf: replace O(n^2) findIndex dedupe with Set-based O(n)
alltheseas Feb 18, 2026
01d7935
fix: parse NIP-51 'relay' tags for kinds 10006/10007
alltheseas Feb 18, 2026
cf4a9f9
fix: verify fallback relays support NIP-50 before injection
alltheseas Feb 18, 2026
49da892
fix: preserve NIP-50 extensions when base query is empty
alltheseas Feb 18, 2026
f95e02d
feat: add mentions: search filter for #p tag queries
alltheseas Feb 18, 2026
ce22043
feat: highlight search terms in results
alltheseas Feb 18, 2026
1617529
feat: show "replying to @username" in NoteHeader (#134)
alltheseas Feb 18, 2026
60e91f5
fix: shared profile resolver with profile relay fallback
alltheseas Feb 18, 2026
94f5260
fix: improve profile and parent event resolution reliability
alltheseas Feb 18, 2026
b94c274
feat: add NIP-66 relay monitor constants
alltheseas Mar 13, 2026
ec4e185
feat: add NIP-66 relay liveness module
alltheseas Mar 13, 2026
f481050
feat: integrate NIP-66 liveness into relay selection
alltheseas Mar 13, 2026
eb5777b
feat: show NIP-66 liveness indicator in relay status display
alltheseas Mar 13, 2026
a3598d1
bench: add NIP-66 before/after benchmark
alltheseas Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,319 changes: 1,319 additions & 0 deletions bench/nip66.ts

Large diffs are not rendered by default.

522 changes: 521 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint && tsc --noEmit",
"test": "jest"
"test": "jest",
"bench:nip66": "tsx bench/nip66.ts"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
Expand Down Expand Up @@ -45,6 +46,7 @@
"jest": "^30.0.5",
"tailwindcss": "^4",
"ts-jest": "^29.1.2",
"tsx": "^4",
"typescript": "^5"
}
}
17 changes: 9 additions & 8 deletions src/components/EventCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { parseHighlightEvent, HIGHLIGHTS_KIND } from '@/lib/highlights';
import { FOLLOW_PACK_KIND } from '@/lib/constants';
import { compareTwoStrings } from 'string-similarity';
import { shortenNpub } from '@/lib/utils';
import { resolveProfileName } from '@/lib/utils/profileUtils';
import { formatUrlResponsive } from '@/lib/utils/urlUtils';
import { nip19 } from 'nostr-tools';
import { ndk } from '@/lib/ndk';
Expand Down Expand Up @@ -116,18 +117,18 @@ export default function EventCard({ event, onAuthorClick, renderContent, variant
let isMounted = true;
(async () => {
try {
const user = new NDKUser({ pubkey: pubkeyHex });
user.ndk = ndk;
try { await user.fetchProfile(); } catch {}
if (!isMounted) return;
const profile = user.profile as { display?: string; displayName?: string; name?: string } | undefined;
const display = profile?.displayName || profile?.display || profile?.name || '';
const npubVal = nip19.npubEncode(pubkeyHex);
setNpub(npubVal);
setLabel(display || `npub:${shortenNpub(npubVal)}`);
const result = await resolveProfileName(pubkeyHex);
if (!isMounted) return;
if (result) {
setLabel(result.isNpubFallback ? result.display : result.display);
} else {
setLabel(shortenNpub(npubVal));
}
} catch {
if (!isMounted) return;
setLabel(`npub:${shortenNpub(nip19.npubEncode(pubkeyHex))}`);
setLabel(shortenNpub(nip19.npubEncode(pubkeyHex)));
}
})();
return () => { isMounted = false; };
Expand Down
21 changes: 9 additions & 12 deletions src/components/InlineNostrToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import { useState, useEffect } from 'react';
import { nip19 } from 'nostr-tools';
import { NDKEvent, NDKRelaySet, type NDKFilter } from '@nostr-dev-kit/ndk';
import { NDKUser } from '@nostr-dev-kit/ndk';
import { ndk, safeSubscribe } from '@/lib/ndk';
import { shortenNpub } from '@/lib/utils';
import { resolveProfileName } from '@/lib/utils/profileUtils';
import { TEXT_MAX_LENGTH } from '@/lib/constants';
import { NOSTR_TOKEN_PARSE_REGEX } from '@/lib/utils/nostrIdentifiers';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand Down Expand Up @@ -102,26 +102,23 @@ export default function InlineNostrToken({
} else {
pubkey = decoded.data as string;
}

const user = new NDKUser({ pubkey });
user.ndk = ndk;
try { await user.fetchProfile(); } catch {}


const result = await resolveProfileName(pubkey);
if (!isMounted) return;

type UserProfileLike = { display?: string; displayName?: string; name?: string } | undefined;
const profile = user.profile as UserProfileLike;
const display = profile?.displayName || profile?.display || profile?.name || '';

const npubVal = nip19.npubEncode(pubkey);

const displayText = result
? (result.isNpubFallback ? result.display : `@${result.display.replace(/^@/, '')}`)
: shortenNpub(npubVal);

setContent(
<button
type="button"
className="text-blue-400 hover:text-blue-300 hover:underline inline"
title={token}
onClick={() => onProfileClick(npubVal)}
>
{display || `npub:${shortenNpub(npubVal)}`}
{displayText}
</button>
);
return;
Expand Down
179 changes: 153 additions & 26 deletions src/components/NoteHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
'use client';

import { useCallback, useEffect, useState } from 'react';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useCallback, useEffect, useRef, useState } from 'react';
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk';
import { nip19 } from 'nostr-tools';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faReply, faSpinner } from '@fortawesome/free-solid-svg-icons';
import { safeSubscribe } from '@/lib/ndk';
import { ndk, safeSubscribe } from '@/lib/ndk';
import { shortenNevent, shortenString } from '@/lib/utils';
import { resolveProfileName, type ProfileResult } from '@/lib/utils/profileUtils';
import RelayIndicator from '@/components/RelayIndicator';
import { getEventKindIcon, getEventKindDisplayName } from '@/lib/eventKindIcons';
import { getKindSearchQuery } from '@/lib/eventKindSearch';
import { FOLLOW_PACK_KIND } from '@/lib/constants';

// Cache: eventId → author pubkey (avoids re-fetching parent events)
// Stores {pubkey, ts} so failed lookups (pubkey=null) expire after NEGATIVE_TTL_MS
const CACHE_MAX = 500;
const NEGATIVE_TTL_MS = 60_000;
const parentAuthorCache = new Map<string, { pubkey: string | null; ts: number }>();
const parentAuthorInflight = new Map<string, Promise<string | null>>();

// --- Component ---

interface NoteHeaderProps {
event: NDKEvent;
expandedParents?: Record<string, NDKEvent | 'loading'>;
Expand Down Expand Up @@ -47,28 +57,47 @@ export default function NoteHeader({
return () => { isMounted = false; };
}, [event.kind]);

const getReplyToEventId = useCallback((event: NDKEvent): string | null => {
const getReplyToInfo = useCallback((event: NDKEvent): { eventId: string; pubkey: string | null; relayHint: string | null } | null => {
try {
const eTags = (event.tags || []).filter((t) => t && t[0] === 'e');
if (eTags.length === 0) return null;

// Deduplicate e tags by event ID to prevent duplicate quoted events

const uniqueETags = new Map<string, typeof eTags[0]>();
eTags.forEach(tag => {
const eventId = tag[1];
if (eventId && !uniqueETags.has(eventId)) {
if (!eventId) return;
const existing = uniqueETags.get(eventId);
const existingHasValidPk = existing?.[4] && /^[0-9a-f]{64}$/i.test(existing[4]);
const tagHasValidPk = tag[4] && /^[0-9a-f]{64}$/i.test(tag[4]);
// Prefer tags that have a validated hex64 pubkey
if (!existing || (!existingHasValidPk && tagHasValidPk)) {
uniqueETags.set(eventId, tag);
}
});
const deduplicatedETags = Array.from(uniqueETags.values());

const replyTag = deduplicatedETags.find((t) => t[3] === 'reply') || deduplicatedETags.find((t) => t[3] === 'root') || deduplicatedETags[deduplicatedETags.length - 1];
return replyTag && replyTag[1] ? replyTag[1] : null;

const hasValidPk = (t: string[]) => t[4] && /^[0-9a-f]{64}$/i.test(t[4]);
// Prefer reply marker, then root marker, then last tag.
// Within each tier, prefer tags that carry a valid pubkey.
const replyTag =
deduplicatedETags.find((t) => t[3] === 'reply' && hasValidPk(t))
|| deduplicatedETags.find((t) => t[3] === 'reply')
|| deduplicatedETags.find((t) => t[3] === 'root' && hasValidPk(t))
|| deduplicatedETags.find((t) => t[3] === 'root')
|| deduplicatedETags[deduplicatedETags.length - 1];
if (!replyTag || !replyTag[1]) return null;
const pubkey = hasValidPk(replyTag) ? replyTag[4] : null;
const relayHint = replyTag[2] && replyTag[2].startsWith('wss://') ? replyTag[2] : null;
return { eventId: replyTag[1], pubkey, relayHint };
} catch {
return null;
}
}, []);

const getReplyToEventId = useCallback((event: NDKEvent): string | null => {
return getReplyToInfo(event)?.eventId ?? null;
}, [getReplyToInfo]);

const fetchEventById = useCallback(async (eventId: string): Promise<NDKEvent | null> => {
return new Promise<NDKEvent | null>((resolve) => {
let found: NDKEvent | null = null;
Expand All @@ -85,14 +114,14 @@ export default function NoteHeader({
}, []);

const parentId = getReplyToEventId(event);

// Find the topmost parent in the chain to show in header
const getTopmostParent = (startEvent: NDKEvent): NDKEvent => {
let currentEvent = startEvent;
while (currentEvent) {
const currentParentId = getReplyToEventId(currentEvent);
if (!currentParentId) break;

const currentParentState = expandedParents[currentParentId];
if (currentParentState && currentParentState !== 'loading' && currentParentState !== null) {
currentEvent = currentParentState as NDKEvent;
Expand All @@ -102,12 +131,104 @@ export default function NoteHeader({
}
return currentEvent;
};

const displayEvent = getTopmostParent(event);

// --- "replying to @username" resolution ---
const replyInfo = getReplyToInfo(displayEvent);
const replyAuthorPubkey = replyInfo?.pubkey ?? null;
const replyEventId = replyInfo?.eventId ?? null;
const replyRelayHint = replyInfo?.relayHint ?? null;
const [replyAuthor, setReplyAuthor] = useState<ProfileResult | null>(null);
const headerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
// Reset on change to prevent stale name from previous ancestor
setReplyAuthor(null);
if (!replyEventId) return;
let isMounted = true;

const resolve = async () => {
// Step 1: determine the parent author pubkey
let pubkey = replyAuthorPubkey;

// If tag[4] didn't have a pubkey, fetch the parent event to get it
if (!pubkey && replyEventId) {
// Check cache first (with TTL — negative entries expire so transient failures retry)
const cached = parentAuthorCache.get(replyEventId);
if (cached && (cached.pubkey !== null || Date.now() - cached.ts < NEGATIVE_TTL_MS)) {
pubkey = cached.pubkey;
} else {
// Dedupe in-flight parent fetches
let fetchPromise = parentAuthorInflight.get(replyEventId);
if (!fetchPromise) {
fetchPromise = (async () => {
try {
// Race the fetch against a timeout to avoid hanging
const timeoutMs = 6000;
const fetchWithTimeout = (relaySet?: NDKRelaySet): Promise<NDKEvent | null> =>
Promise.race([
ndk.fetchEvent({ ids: [replyEventId] }, {}, relaySet),
new Promise<null>(r => setTimeout(() => r(null), timeoutMs))
]);

// Try relay hint first if available, then fall back to default relays
let evt: NDKEvent | null = null;
if (replyRelayHint) {
evt = await fetchWithTimeout(NDKRelaySet.fromRelayUrls([replyRelayHint], ndk));
}
if (!evt) {
evt = await fetchWithTimeout();
}
const authorPk = evt?.pubkey ?? null;
if (parentAuthorCache.size >= CACHE_MAX) {
const firstKey = parentAuthorCache.keys().next().value;
if (firstKey) parentAuthorCache.delete(firstKey);
}
parentAuthorCache.set(replyEventId, { pubkey: authorPk, ts: Date.now() });
return authorPk;
} catch {
parentAuthorCache.set(replyEventId, { pubkey: null, ts: Date.now() });
return null;
} finally {
parentAuthorInflight.delete(replyEventId);
}
})();
parentAuthorInflight.set(replyEventId, fetchPromise);
}
pubkey = await fetchPromise;
}
}

if (!isMounted) return;
if (!pubkey) return;

// Step 2: resolve the pubkey to a display name
const result = await resolveProfileName(pubkey);
if (isMounted) setReplyAuthor(result);
};

// Guard: use IntersectionObserver if available, otherwise resolve immediately
if (typeof IntersectionObserver === 'undefined' || !headerRef.current) {
resolve();
return () => { isMounted = false; };
}

const observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) return;
observer.disconnect();
resolve();
},
{ threshold: 0 }
);
observer.observe(headerRef.current);
return () => { isMounted = false; observer.disconnect(); };
}, [replyAuthorPubkey, replyEventId, replyRelayHint]);

const handleToggle = async () => {
if (!topmostParentId || !onParentToggle) return;

if (expandedParents[topmostParentId]) {
onParentToggle(topmostParentId, null);
return;
Expand All @@ -126,16 +247,16 @@ export default function NoteHeader({

const isReply = Boolean(parentId);
const barClasses = `text-xs text-gray-300 border border-[#3d3d3d] border-b-0 px-4 py-2 rounded-t-lg rounded-b-none ${
isReply
? 'bg-[#262626]'
isReply
? 'bg-[#262626]'
: 'bg-[#353535]'
} ${className}`;

// Get the parent ID for the topmost parent (for next expansion)
const topmostParentId = getReplyToEventId(displayEvent);
const topmostParentState = topmostParentId ? expandedParents[topmostParentId] : null;
const isLoading = topmostParentState === 'loading';

// Extract filename for code events (supports tags: name, f, title)
const isCodeEvent = displayEvent.kind === 1337 || displayEvent.kind === 1617;
const getTagValue = (keys: string[]): string | null => {
Expand All @@ -149,11 +270,11 @@ export default function NoteHeader({
return null;
};
const fileName = isCodeEvent ? (getTagValue(['name', 'f', 'title']) || null) : null;

// Extract follow pack title
const isFollowPack = displayEvent.kind === FOLLOW_PACK_KIND;
const followPackTitle = isFollowPack ? (getTagValue(['title']) || null) : null;

const parentLabel = (() => {
if (!topmostParentId) return null;
const normalized = topmostParentId.trim();
Expand All @@ -166,20 +287,26 @@ export default function NoteHeader({
})();

return (
<div className={`${barClasses} border-t border-[#3d3d3d]`}>
<div ref={headerRef} className={`${barClasses} border-t border-[#3d3d3d]`}>
<div className="flex items-center justify-between w-full">
{topmostParentId ? (
<button
type="button"
onClick={handleToggle}
<button
type="button"
onClick={handleToggle}
className="flex-1 text-left flex items-center gap-2 h-6"
>
{isLoading ? (
<FontAwesomeIcon icon={faSpinner} className="text-xs text-gray-400 animate-spin" />
) : (
<>
<FontAwesomeIcon icon={faReply} className="text-xs text-gray-400 transform -rotate-270 scale-y-[-1]" />
<span>{parentLabel}</span>
<span>
{replyAuthor
? <>replying to <span className="text-blue-400">
{replyAuthor.isNpubFallback ? replyAuthor.display : `@${replyAuthor.display.replace(/^@/, '')}`}
</span></>
: parentLabel}
</span>
</>
)}
</button>
Expand Down Expand Up @@ -208,7 +335,7 @@ export default function NoteHeader({
{followPackTitle ? (
<span className="text-gray-200 truncate font-semibold" title={followPackTitle}>{followPackTitle}</span>
) : null}

</>
);
})()}
Expand Down
Loading