From c79f4122dae9c4efab0d4c1baec7689c3c8294af Mon Sep 17 00:00:00 2001 From: Gigi Date: Sat, 18 Oct 2025 23:57:46 +0200 Subject: [PATCH] feat(debug): add Writings Loading section to debug page - Add handlers for loading my writings, friends writings, and nostrverse writings - Display writings with title, summary, author, and d-tag - Show timing metrics (total load time and first event time) - Use writingsController for own writings to test controller functionality --- src/components/Debug.tsx | 283 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx index cfde73b3..b6665da1 100644 --- a/src/components/Debug.tsx +++ b/src/components/Debug.tsx @@ -18,6 +18,8 @@ import { useBookmarksUI } from '../hooks/useBookmarksUI' import { useSettings } from '../hooks/useSettings' import { fetchHighlights, fetchHighlightsFromAuthors } from '../services/highlightService' import { contactsController } from '../services/contactsController' +import { writingsController } from '../services/writingsController' +import { fetchBlogPostsFromAuthors, BlogPostPreview } from '../services/exploreService' const defaultPayload = 'The quick brown fox jumps over the lazy dog.' @@ -94,6 +96,12 @@ const Debug: React.FC = ({ const [tLoadHighlights, setTLoadHighlights] = useState(null) const [tFirstHighlight, setTFirstHighlight] = useState(null) + // Writings loading state + const [isLoadingWritings, setIsLoadingWritings] = useState(false) + const [writingPosts, setWritingPosts] = useState([]) + const [tLoadWritings, setTLoadWritings] = useState(null) + const [tFirstWriting, setTFirstWriting] = useState(null) + // Live timing state const [liveTiming, setLiveTiming] = useState<{ nip44?: { type: 'encrypt' | 'decrypt'; startTime: number } @@ -538,6 +546,188 @@ const Debug: React.FC = ({ } } + const handleLoadMyWritings = async () => { + if (!relayPool || !activeAccount?.pubkey || !eventStore) { + DebugBus.warn('debug', 'Please log in to load your writings') + return + } + const start = performance.now() + setWritingPosts([]) + setIsLoadingWritings(true) + setTLoadWritings(null) + setTFirstWriting(null) + DebugBus.info('debug', 'Loading my writings via writingsController...') + try { + let firstEventTime: number | null = null + const unsub = writingsController.onWritings((posts) => { + if (firstEventTime === null && posts.length > 0) { + firstEventTime = performance.now() - start + setTFirstWriting(Math.round(firstEventTime)) + } + setWritingPosts(posts) + }) + + await writingsController.start({ + relayPool, + eventStore, + pubkey: activeAccount.pubkey, + force: true + }) + + unsub() + const currentWritings = writingsController.getWritings() + setWritingPosts(currentWritings) + DebugBus.info('debug', `Loaded ${currentWritings.length} writings via controller`) + } finally { + setIsLoadingWritings(false) + const elapsed = Math.round(performance.now() - start) + setTLoadWritings(elapsed) + DebugBus.info('debug', `Loaded my writings in ${elapsed}ms`) + } + } + + const handleLoadFriendsWritings = async () => { + if (!relayPool || !activeAccount?.pubkey) { + DebugBus.warn('debug', 'Please log in to load friends writings') + return + } + const start = performance.now() + setWritingPosts([]) + setIsLoadingWritings(true) + setTLoadWritings(null) + setTFirstWriting(null) + DebugBus.info('debug', 'Loading friends writings...') + try { + // Get contacts first + await contactsController.start({ relayPool, pubkey: activeAccount.pubkey }) + const friends = contactsController.getContacts() + const friendsArray = Array.from(friends) + DebugBus.info('debug', `Found ${friendsArray.length} friends`) + + if (friendsArray.length === 0) { + DebugBus.warn('debug', 'No friends found to load writings from') + return + } + + let firstEventTime: number | null = null + const relayUrls = Array.from(relayPool.relays.values()).map(relay => relay.url) + const posts = await fetchBlogPostsFromAuthors( + relayPool, + friendsArray, + relayUrls, + (post) => { + if (firstEventTime === null) { + firstEventTime = performance.now() - start + setTFirstWriting(Math.round(firstEventTime)) + } + setWritingPosts(prev => { + const dTag = post.event.tags.find(t => t[0] === 'd')?.[1] || '' + const key = `${post.author}:${dTag}` + const exists = prev.find(p => { + const pDTag = p.event.tags.find(t => t[0] === 'd')?.[1] || '' + return `${p.author}:${pDTag}` === key + }) + if (exists) return prev + return [...prev, post].sort((a, b) => { + const timeA = a.published || a.event.created_at + const timeB = b.published || b.event.created_at + return timeB - timeA + }) + }) + } + ) + + setWritingPosts(posts) + DebugBus.info('debug', `Loaded ${posts.length} friend writings`) + } finally { + setIsLoadingWritings(false) + const elapsed = Math.round(performance.now() - start) + setTLoadWritings(elapsed) + DebugBus.info('debug', `Loaded friend writings in ${elapsed}ms`) + } + } + + const handleLoadNostrverseWritings = async () => { + if (!relayPool) { + DebugBus.warn('debug', 'Relay pool not available') + return + } + const start = performance.now() + setWritingPosts([]) + setIsLoadingWritings(true) + setTLoadWritings(null) + setTFirstWriting(null) + DebugBus.info('debug', 'Loading nostrverse writings (kind:30023)...') + try { + let firstEventTime: number | null = null + const relayUrls = Array.from(relayPool.relays.values()).map(relay => relay.url) + + const { queryEvents } = await import('../services/dataFetch') + const { Helpers } = await import('applesauce-core') + const { getArticleTitle, getArticleSummary, getArticleImage, getArticlePublished } = Helpers + + const uniqueEvents = new Map() + await queryEvents(relayPool, { kinds: [30023], limit: 50 }, { + relayUrls, + onEvent: (evt) => { + const dTag = evt.tags.find(t => t[0] === 'd')?.[1] || '' + const key = `${evt.pubkey}:${dTag}` + const existing = uniqueEvents.get(key) + if (!existing || evt.created_at > existing.created_at) { + uniqueEvents.set(key, evt) + + if (firstEventTime === null) { + firstEventTime = performance.now() - start + setTFirstWriting(Math.round(firstEventTime)) + } + + const posts = Array.from(uniqueEvents.values()).map(event => ({ + event, + title: getArticleTitle(event) || 'Untitled', + summary: getArticleSummary(event), + image: getArticleImage(event), + published: getArticlePublished(event), + author: event.pubkey + } as BlogPostPreview)).sort((a, b) => { + const timeA = a.published || a.event.created_at + const timeB = b.published || b.event.created_at + return timeB - timeA + }) + + setWritingPosts(posts) + } + } + }) + + const finalPosts = Array.from(uniqueEvents.values()).map(event => ({ + event, + title: getArticleTitle(event) || 'Untitled', + summary: getArticleSummary(event), + image: getArticleImage(event), + published: getArticlePublished(event), + author: event.pubkey + } as BlogPostPreview)).sort((a, b) => { + const timeA = a.published || a.event.created_at + const timeB = b.published || b.event.created_at + return timeB - timeA + }) + + setWritingPosts(finalPosts) + DebugBus.info('debug', `Loaded ${finalPosts.length} nostrverse writings`) + } finally { + setIsLoadingWritings(false) + const elapsed = Math.round(performance.now() - start) + setTLoadWritings(elapsed) + DebugBus.info('debug', `Loaded nostrverse writings in ${elapsed}ms`) + } + } + + const handleClearWritings = () => { + setWritingPosts([]) + setTLoadWritings(null) + setTFirstWriting(null) + } + const handleLoadFriendsList = async () => { if (!relayPool || !activeAccount?.pubkey) { DebugBus.warn('debug', 'Please log in to load friends list') @@ -1070,6 +1260,99 @@ const Debug: React.FC = ({ )} + {/* Writings Loading Section */} +
+

Writings Loading

+ +
Quick load options:
+
+ + + + +
+ +
+ + +
+ + {writingPosts.length > 0 && ( +
+
Loaded Writings ({writingPosts.length}):
+
+ {writingPosts.map((post, idx) => { + const title = post.title + const summary = post.summary + const dTag = post.event.tags.find(t => t[0] === 'd')?.[1] || '' + + return ( +
+
Writing #{idx + 1}
+
+
Author: {post.author.slice(0, 16)}...
+
Published: {post.published ? new Date(post.published * 1000).toLocaleString() : new Date(post.event.created_at * 1000).toLocaleString()}
+
d-tag: {dTag || '(empty)'}
+
+
+
Title:
+
"{title}"
+
+ {summary && ( +
+
Summary: {summary.substring(0, 100)}{summary.length > 100 ? '...' : ''}
+
+ )} + {post.image && ( +
+
Image: {post.image.substring(0, 40)}...
+
+ )} +
ID: {post.event.id}
+
+ ) + })} +
+
+ )} +
+ {/* Web of Trust Section */}

Web of Trust