### FeatureGate Usage Examples (TypeScript) Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/05-progressive-complexity.md Demonstrates practical applications of the `FeatureGate` component within React components. Includes examples for gating toolbar buttons and settings menu items based on different user levels. ```typescript // Usage examples function ComposerToolbar() { return (
{/* Always visible */} {/* Intermediate+ */} {/* Advanced+ */} Unlock custom relay selection after 30 days of activity } > {/* Power users only */}
) } function SettingsMenu() { const userLevel = getUserLevel(useUser()) return (
{/* Basic settings - always visible */} Profile Notifications Display {/* Advanced settings - gated */} Advanced {/* Power user prompt */} {userLevel === UserLevel.ADVANCED && ( Enable Power User Mode Unlock all features )}
) } ``` -------------------------------- ### Initialize New User with Smart Default Follows (JavaScript) Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt Automatically follows quality accounts upon user signup to address the cold start problem. It selects a starter pack of users based on provided interests and publishes a kind:3 event to relays. ```javascript const STARTER_PACKS = { 'tech': [ { npub: 'npub1tech...', reason: 'Top dev educator' }, { npub: 'npub1oss...', reason: 'OSS advocate' } ], 'art': [ { npub: 'npub1artist...', reason: 'Digital art curator' }, { npub: 'npub1photo...', reason: 'Photography community' } ] }; async function initializeNewUser(interests) { // Select starter pack based on user interests const follows = STARTER_PACKS[interests] || STARTER_PACKS['tech']; // Create kind:3 event with auto-follows const contactList = await createContactListEvent(follows); await publishToRelays(contactList, defaultRelays); // Show immediate value return { message: `Following ${follows.length} accounts to get you started`, nextStep: 'View your personalized feed', feedSize: await estimateFeedSize(follows) }; } ``` -------------------------------- ### Context Injection using Manifest (JavaScript) Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt An example function demonstrating how to load a `manifest.json` file and use it to prepare context for an LLM. It fetches the manifest, filters for relevant content based on a topic, loads the full content of matching items, and returns a structured context object including the query, relevant patterns, and metadata. ```javascript // Example: Load manifest into LLM context async function prepareContextForLLM(topic) { const manifest = await fetch('/manifest.json').then(r => r.json()); // Find relevant patterns const relevant = manifest.content.filter(item => item.keywords.includes(topic) || item.category === topic ); // Load full content const context = await Promise.all( relevant.map(async item => { const content = await fetch(item.url).then(r => r.text()); return { ...item, content }; }) ); return { query: topic, relevantPatterns: context, metadata: manifest }; } // Usage: Prepare context for "onboarding" question const context = await prepareContextForLLM('onboarding'); // Returns Pattern 1, Pattern 5, and related research ``` -------------------------------- ### Track onboarding funnel events with JavaScript Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/implementation/measuring-success.md This snippet demonstrates how to record key onboarding metrics such as signup start, signup completion, and first value reached using an analytics library. It depends on an `analytics.track` function that accepts an event name and payload. The code logs timestamps and durations, enabling later analysis of conversion rates and time‑to‑value. ```JavaScript // Track funnel events analytics.track('signup_started', { timestamp }) analytics.track('signup_completed', { timestamp, duration }) analytics.track('first_value_reached', { action: 'viewed_feed' }) ``` -------------------------------- ### Gradual Integration with Feature Flags (JavaScript) Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt Demonstrates how to integrate new features incrementally using a class-based approach with feature flags. This allows for progressive enhancement and fallback to legacy functionality without breaking existing systems. It includes examples for publishing posts and loading feeds, with specific flags for optimistic UI and smart caching. ```javascript // Pattern: Gradual integration without breaking changes class NostrClientRefactor { constructor(legacyClient) { this.legacy = legacyClient; this.enhanced = new EnhancedNostrClient(); this.featureFlags = new FeatureFlags(); } async publishPost(content) { // Feature flag to gradually roll out new patterns if (this.featureFlags.enabled('optimistic_ui')) { return this.enhanced.publishWithOptimisticUI(content); } else { return this.legacy.publish(content); } } async loadFeed() { // Progressive enhancement - try new, fallback to old try { if (this.featureFlags.enabled('smart_caching')) { return await this.enhanced.loadFeedWithCache(); } } catch (err) { logger.warn('Enhanced feed failed, falling back', err); } return this.legacy.loadFeed(); } } // Feature flags for gradual rollout const FLAGS = { 'optimistic_ui': { enabled: true, rollout: 100 }, 'smart_caching': { enabled: true, rollout: 50 }, 'guest_mode': { enabled: false, rollout: 0 } }; ``` -------------------------------- ### Virtual Scrolling Example for Feed Rendering Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/04-performance.md Demonstrates the difference between rendering all items directly versus using virtual scrolling for large lists. Virtual scrolling significantly improves performance by only rendering visible items. ```typescript // Bad: Render all 1000 posts { posts.map(post => ) } // Good: Virtual scrolling } /> ``` -------------------------------- ### Smart Defaults Configuration Object in JavaScript Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt Defines a default configuration object for Nostr client settings, designed to provide a good user experience out-of-the-box for most users. It includes sensible defaults for relays, content display, and privacy settings, aiming to reduce the need for manual configuration. This object serves as a baseline for new user setups. ```javascript // Work great without configuration for 80% of users const DEFAULT_CONFIG = { relays: { // Curated list of reliable relays read: [ 'wss://relay.damus.io', 'wss://relay.nostr.band', 'wss://nos.lol' ], write: [ 'wss://relay.damus.io', 'wss://nos.lol' ], // Automatically expand based on followed users' relay hints autoExpand: true, maxRelays: 10 }, content: { autoFollowStarterPack: true, starterPackCategory: 'popular', // or 'tech', 'art', etc. showTrending: true }, privacy: { // Sensible privacy defaults showReadReceipts: false, shareAnalytics: false, dmEncryption: 'always' } }; ``` -------------------------------- ### Onboarding: Guest Browse Mode Implementation in JavaScript Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt Demonstrates a JavaScript implementation for an onboarding flow that allows users to consume content in a 'guest' mode before requiring account creation. It focuses on reducing time-to-first-value by fetching default content immediately and prompting signup after a set number of interactions. ```javascript // Allow content consumption before requiring account creation class OnboardingFlow { constructor() { this.mode = 'guest'; // Start in browse mode this.relays = DEFAULT_RELAYS; // Smart defaults } async loadFeed() { // Show curated content immediately const defaultFollows = await fetchStarterPack('popular-tech'); const posts = await fetchPostsFromRelays(defaultFollows, this.relays); return posts; // User sees value in <5 seconds } promptSignup() { // Only prompt after user has browsed 10+ posts if (this.postsViewed >= 10) { showModal({ title: "Join to post and follow", cta: "Create account (30 seconds)", alternative: "Continue browsing" }); } } } ``` -------------------------------- ### UX Research Evidence Collection Patterns in JavaScript Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt JavaScript object illustrating examples of good and bad evidence collection for validating a feature's problem statement. 'Good' examples show structured evidence from metrics, research, and user complaints, while 'bad' shows an assumption-based approach lacking concrete data. ```javascript // Evidence collection patterns const VALIDATION_EXAMPLES = { good: { feature: 'Guest browse mode', evidence: [ { type: 'metrics', source: 'analytics', finding: '70% of visitors bounce before signup', url: 'https://nostr.band/stats' }, { type: 'research', source: 'academic', finding: 'TikTok retention 2x higher with browse-first', url: 'https://example.com/study' }, { type: 'user_complaints', source: 'github_issues', finding: '15 issues complaining about forced signup', urls: ['issue#123', 'issue#456'] } ] }, bad: { feature: 'Advanced relay configuration', evidence: [ { type: 'assumption', source: 'developer_opinion', finding: 'Users probably want this', url: null // No evidence } ] } }; ``` -------------------------------- ### Anti-Pattern: Synchronous Blocking Operations (TypeScript) Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/04-performance.md Demonstrates a harmful anti-pattern where synchronous operations block the User Interface during feed loading. This makes the application unresponsive and provides a poor user experience. The example shows a loop performing blocking queries. ```typescript function loadFeed() { showSpinner() // Blocks UI for 10+ seconds for (const relay of relays) { const posts = relay.query(filter) // Synchronous allPosts.push(...posts) } renderFeed(allPosts) hideSpinner() } ``` -------------------------------- ### Handling Nostr Event Publishing with Relay Feedback Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/03-core-interactions.md This TypeScript example demonstrates a robust pattern for publishing Nostr events across multiple relays. It emphasizes showing events as pending immediately, awaiting confirmation from a minimum number of relays, and providing partial success feedback while retrying failed relays. ```typescript const event = createEvent(content) showAsPending(event) // Show as "sending" const results = await publishToRelays(event) if (results.successCount >= MIN_RELAYS) { showAsPublished(event) } else { showAsPartiallyPublished(event, results) retryFailedRelays(event, results.failed) } ``` -------------------------------- ### Three-Tier Settings UI in JavaScript Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt Implements a progressive settings UI that transitions from basic to advanced and expert levels based on user interaction. It defines the structure for each setting tier and dynamically renders sections based on the user's experience level, inferred from signup duration and post count. Dependencies include a React-like rendering environment. ```javascript // Progressive settings: Basic → Advanced → Expert const SETTINGS_STRUCTURE = { basic: [ { id: 'notifications', label: 'Notifications', type: 'toggle' }, { id: 'theme', label: 'Dark mode', type: 'toggle' }, { id: 'language', label: 'Language', type: 'select' } ], advanced: [ { id: 'relay_health', label: 'Show relay status', type: 'toggle' }, { id: 'media_quality', label: 'Media quality', type: 'select' }, { id: 'cache_size', label: 'Cache size', type: 'slider' } ], expert: [ { id: 'custom_relays', label: 'Custom relay list', type: 'list' }, { id: 'read_write_split', label: 'Separate read/write relays', type: 'toggle' }, { id: 'nip46_signer', label: 'External signer (NIP-46)', type: 'config' } ] }; class SettingsView { render() { const userLevel = this.getUserExperienceLevel(); return ( <> {userLevel >= 'intermediate' && ( )} {userLevel === 'expert' && ( )} ); } getUserExperienceLevel() { const daysActive = this.getDaysSinceSignup(); const postsCreated = this.getPostCount(); if (daysActive < 7 || postsCreated < 10) return 'beginner'; if (daysActive < 30 || postsCreated < 50) return 'intermediate'; return 'expert'; } } ``` -------------------------------- ### TypeScript: Automatic Relay Selection Logic Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/05-progressive-complexity.md Implements automatic selection of optimal Nostr relays based on user location and latency testing. It defines a class `AutomaticRelayManager` with methods to get user location, fetch default regional and global relays, and test their latency. Dependencies include `getUserLocation`, `pingRelay`, and `RelayStats`/`RelayTestResult` types. ```typescript class AutomaticRelayManager { private userLocation: string private relayStats: Map async getOptimalRelays(): Promise { const location = await this.getUserLocation() const defaults = this.getDefaultRelays(location) const tested = await this.testRelayLatency(defaults) // Return 3-4 fastest, most reliable relays return tested .sort((a, b) => a.latency - b.latency) .slice(0, 4) .map(r => r.url) } private getDefaultRelays(location: string): string[] { const globalRelays = [ 'wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.nostr.band' ] const regionalRelays = { 'NA': ['wss://nostr.mom', 'wss://relay.current.fyi'], 'EU': ['wss://nostr.wine', 'wss://relay.orangepill.dev'], 'APAC': ['wss://relay.nostr.wirednet.jp', 'wss://relay.nostr.moctane.com'] } return [...globalRelays, ...(regionalRelays[location] || [])] } private async testRelayLatency(relays: string[]): Promise { const results = await Promise.all( relays.map(async url => { const start = Date.now() try { await this.pingRelay(url) return { url, latency: Date.now() - start, online: true } } catch { return { url, latency: Infinity, online: false } } }) ) return results.filter(r => r.online) } } ``` -------------------------------- ### Implement Feature Flags with Conditional Logic (JavaScript) Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/implementation/getting-started.md Demonstrates how to use feature flags to conditionally enable new features. This pattern allows for A/B testing or gradual rollouts by checking the state of a feature flag before executing new or old code paths. It's crucial for managing releases and user experience changes. ```javascript if (featureFlags.optimisticUI) { // New optimistic behavior updateUIImmediately(post) syncToRelaysInBackground(post) } else { // Old behavior await syncToRelays(post) updateUI(post) } ``` -------------------------------- ### Avoid Silent Failure in Post Publishing with TypeScript Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/03-core-interactions.md This example demonstrates an anti-pattern of silent failures in publishing posts, where errors are ignored. It depends on event creation and relay publishing, takes content as input, and silently fails without user notification. This pattern is discouraged as it erodes trust and prevents retries; alternatives involve explicit error handling and user feedback. ```typescript async function publishPost(content) { const event = createEvent(content) try { await publishToRelays(event) } catch (error) { // Do nothing - silent failure } // User thinks post published, but it failed } ``` -------------------------------- ### JavaScript: Implementing Optimistic UI Updates Safely Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/06-cross-client-consistency.md This JavaScript snippet illustrates an optimistic UI update pattern for following a user. It highlights the potential for data loss by assuming the `publishKind3` operation succeeds without validation. The code adds the new public key to `currentFollows`, immediately publishes the event, and updates the UI, but lacks error handling or confirmation. ```javascript function followUser(pubkey) { currentFollows.push(pubkey); publishKind3(currentFollows); // Fire and forget updateUI(); // Assume it worked } ``` -------------------------------- ### Implement Educational Tooltip System with React and TypeScript Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/05-progressive-complexity.md Creates a reusable React component for contextual feature education using tooltips. Accepts feature metadata and children, persists user acknowledgment in localStorage, and displays a badge indicator for unseen tips. Integrates with a Tooltip wrapper to provide dismissible educational content with optional external links, designed for progressive disclosure of complex Nostr concepts like relay configurations. ```tsx // Educational tooltip system function EducationalTooltip({ feature, title, description, learnMoreUrl, children }: { feature: string title: string description: string learnMoreUrl?: string children: React.ReactNode }) { const [hasSeenTooltip, setHasSeenTooltip] = useState( () => localStorage.getItem(`tooltip-seen-${feature}`) === 'true' ) const [isOpen, setIsOpen] = useState(false) const markAsSeen = () => { localStorage.setItem(`tooltip-seen-${feature}`, 'true') setHasSeenTooltip(true) setIsOpen(false) } return (

{title}

{description}

{learnMoreUrl && ( Learn more → )}
} badge={!hasSeenTooltip ? '!' : undefined} > {children}
) } // Usage in advanced settings function AdvancedNetworkSettings() { return (
) } ``` -------------------------------- ### Show Contextual Hints (TypeScript) Source: https://github.com/shawnyeager/nostr-ux-research/blob/master/content/docs/patterns/05-progressive-complexity.md This function uses user behavior tracking to display contextual hints based on pre-defined conditions. It prevents repeated hints and provides actions for users to explore relevant features, persisting user preferences. ```TypeScript // Track user behavior to detect power user patterns interface UserBehaviorTracker { daysActive: number postsCount: number followingCount: number settingsVisits: number hasSeenHint: Set } function shouldShowHint( hintId: string, user: UserBehaviorTracker, condition: () => boolean ): boolean { // Never show same hint twice if (user.hasSeenHint.has(hintId)) return false // Check condition return condition() } // Contextual hint system function useContextualHints(user: UserBehaviorTracker) { const [currentHint, setCurrentHint] = useState(null) useEffect(() => { // Power user detection: Show relay optimization hint if (shouldShowHint('relay-optimization', user, () => user.daysActive > 30 && user.postsCount > 50 )) { setCurrentHint({ id: 'relay-optimization', title: '💡 Did you know?', message: 'You can optimize your network settings for better performance', action: { label: 'Learn more', onClick: () => navigate('/settings/advanced/network') } }) } // Frequent poster: Show backup reminder if (shouldShowHint('backup-reminder', user, () => user.postsCount > 10 && !user.hasBackup )) { setCurrentHint({ id: 'backup-reminder', title: '🔒 Secure your account', message: 'You've posted 10 times! Consider backing up your account', action: { label: 'Set up backup', onClick: () => navigate('/settings/security/backup') } }) } // Heavy follower: Show custom feed hint if (shouldShowHint('custom-feeds', user, () => user.followingCount > 100 )) { setCurrentHint({ id: 'custom-feeds', title: '📋 Organize your feed', message: 'You follow 100+ people. Try creating custom feeds to organize content', action: { label: 'Create feed', onClick: () => navigate('/feeds/create') } }) } }, [user]) const dismissHint = (hintId: string) => { user.hasSeenHint.add(hintId) setCurrentHint(null) // Persist to storage saveDismissedHints(user.hasSeenHint) } return { currentHint, dismissHint } } // UI Component function ContextualHintBanner({ hint, onDismiss }) { if (!hint) return null return (

{hint.title}

{hint.message}

) } ``` -------------------------------- ### Implement Skeleton Screens in JavaScript for Nostr Feed Source: https://context7.com/shawnyeager/nostr-ux-research/llms.txt Shows loading placeholders immediately while data loads from relays. Uses React components to progressively update the UI as data arrives from the fastest relays first. ```javascript // Show loading placeholders immediately, populate as data arrives class FeedView { render() { return ( {this.state.posts.map(post => ( post.isLoading ? : ))} ); } async loadFeed() { // Show 10 skeleton placeholders immediately this.setState({ posts: Array(10).fill().map(() => ({ isLoading: true, id: uuid() })) }); // Fetch from fastest relays first const relaysBySpeed = this.sortRelaysByLatency(); for (const relay of relaysBySpeed) { const posts = await relay.query({ kinds: [1], limit: 20 }); // Update UI as each relay responds (progressive enhancement) this.setState(prev => ({ posts: this.mergePosts(prev.posts, posts) })); // Stop after we have enough content if (this.state.posts.filter(p => !p.isLoading).length >= 20) { break; } } } } ```