### 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 */}
)
}
```
--------------------------------
### 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 (