Pattern 5: Progressive Complexity

Pattern 5: Progressive Complexity

Problem Statement

Current State

Overwhelming complexity killing adoption:

  • New users exposed to relay selection, key management, and NIP configurations immediately during onboarding
  • Nostr clients showing 50+ relay pickers, signer app setup (NIP-46), and read/write relay splits before users understand basic posting
  • Settings pages with technical jargon: “Outbox model (NIP-65)”, “Relay list metadata”, “Event signature verification”
  • Users report feeling “overwhelmed” and “confused” by protocol complexity exposed in UI
  • Power user features (relay health indicators, custom feeds, mute lists) presented as essential setup steps

The perception problem: Nostr apps feel like developer tools, not consumer apps. Users compare to Twitter/Instagram’s “it just works” simplicity and abandon when faced with:

  • Relay management decisions on first launch
  • Key signer configuration requirements
  • Technical error messages referencing NIPs
  • No sensible defaults—everything requires user configuration

The retention impact:

  • Users abandon during onboarding when faced with unfamiliar technical concepts
  • “Too complicated” cited as top reason for not adopting Nostr
  • Power users love the control, but they represent <5% of potential audience
  • Missing the mainstream market by optimizing for protocol purists

Root Causes

  1. Protocol complexity exposed directly to users: Relays, NIPs, and cryptographic concepts in user-facing UI
  2. No smart defaults: Apps require configuration instead of working great out-of-box
  3. Power user features treated as essential: Relay selection, signer apps shown to all users upfront
  4. Developer-first design: Built for people who understand Nostr protocol, not mainstream users
  5. Feature bloat without hierarchy: All features given equal prominence regardless of usage frequency
  6. Poor feature discovery: Advanced features either hidden completely or overwhelming beginners

Why This Matters

The Pareto Principle: “80% of users will only use 20% of features. Design for that 80% first.” [Research:54]

Research shows: [Research:55] [Research:56] [Research:57]

  • 30-40% reduction in cognitive load through progressive disclosure
  • 25% fewer interactions needed with smart defaults (Airbnb)
  • 24% reduction in cognitive load from decluttered designs (Figma)
  • Users abandon apps with >10-15 settings on a single screen [Research:58]

Key insight: Focus on the 20% of features that deliver 80% of user value. Complexity should be optional, not mandatory.


Universal Principles

These principles apply to any application managing feature complexity and user experience.

1. The Pareto Principle

Research backing: [Research:54] [Research:59]

Core insight: 80% of users will use only 20% of features. Design should optimize for the majority, not the power user minority.

Application to interface design:

  • Feature prioritization: Identify which 20% of features meet 80% of users’ needs [Research:59]
  • Design focus: 20% of design decisions drive 80% of impact—focus there [Research:59]
  • MVP development: Minimum viable products are Pareto Principle in action [Research:59]

Key strategies:

  • Default to the 20% of features that deliver 80% of value
  • Make power features discoverable, not prominent
  • Measure feature usage—if <20% use it, it shouldn’t be in primary UI
  • Reduce cognitive load by simplifying design elements [Research:59]

Examples from mainstream apps:

  • Email clients: Compose/Read/Archive visible; filters/labels/rules in settings
  • Social apps: Post/Like/Comment primary; mute lists/quality filters advanced
  • Photo apps: Basic filters prominent; curves/levels/masking for pros

2. Progressive Disclosure

Research backing: [Research:55] [Research:56] [Research:60]

Core insight: Defer advanced features and information to secondary UI components. Keep essential content in primary UI, reveal advanced content upon request. [Research:60]

Why it works:

  • 30-40% reduction in cognitive load during onboarding [Research:55]
  • Reduces decision fatigue by revealing information in stages [Research:56]
  • Breaks complex processes into manageable steps [Research:55]
  • Eliminates friction points that cause abandonment [Research:55]

Common implementations: [Research:56] [Research:60]

  • Collapsible menus: Hide advanced options until clicked
  • Tooltips/contextual help: Information appears on hover or interaction
  • Hover-based actions: Secondary actions revealed on hover
  • Toggles: Reveal or hide advanced settings, letting users control interface complexity [Research:60]
  • Step-by-step flows: Multi-page wizards instead of one overwhelming screen

When to use: [Research:60]

  • Designing for novice users
  • Complex tasks with many options
  • Limited screen space (mobile)
  • High cognitive load scenarios

Design methods: [Research:60] Define essential vs. advanced content through user research using card sorting and task analysis.

3. Smart Defaults

Research backing: [Research:57] [Research:61]

Core insight: Great UX isn’t about hiding features—it’s about organizing them so users can find, understand, and use them without frustration. [Research:61]

The default experience should:

  • Work excellently without any configuration
  • Serve 80% of users’ needs immediately
  • Allow customization for the 20% who need it
  • Never force decisions users aren’t ready to make

Airbnb case study: [Research:57]

  • Smart defaults led to 25% fewer interactions needed to complete bookings
  • Higher conversion rate with less cognitive effort
  • Users could still customize, but most didn’t need to

Best practices:

  • Research what most users actually need (not what power users want)
  • Test defaults with non-technical users
  • Make changing defaults easy, but unnecessary for most
  • Provide “Reset to default” option for users who customize

Examples:

  • Notification settings: Defaults that balance engagement without spam
  • Privacy settings: Sensible privacy by default, opt-in for sharing
  • Display settings: Auto dark mode, readable font sizes, standard layouts

4. Settings Hierarchy

Research backing: [Research:58] [Research:61]

Core insight: Limit settings on one screen to 10-15 items—more becomes overwhelming. [Research:58]

Create intuitive menus by moving additional settings to separate screens.

Three-tier hierarchy:

Tier 1: Basic (Visible to all)

  • Frequently used settings (top of screen) [Research:58]
  • Core functionality toggles
  • Account/profile settings
  • <10 items maximum

Tier 2: Advanced (Behind “Advanced Settings” section)

  • Features used by 20-40% of users
  • Customization options
  • Performance tweaks
  • Grouped logically by category

Tier 3: Expert (Hidden in debug/developer/power user mode)

  • Experimental features
  • Technical configurations
  • Developer tools
  • Risk: Can break things if misused

Android design guidelines: [Research:58]

  • Place frequently used settings at top
  • Users should glance at settings and understand all individual settings and their values
  • Limit items per screen to avoid overwhelming users

Visual hierarchy principles: [Research:61]

  • Prioritize important information
  • Ensure content aligns with user needs and goals
  • Use shared traits (color, shape, size) for quick understanding
  • Apply visual consistency

5. Contextual Help

Research backing: [Research:62] [Research:63]

Core insight: Show help content alongside each step to minimize cognitive load, rather than overwhelming users upfront. [Research:62]

Pull revelations vs. Push tutorials: [Research:62]

  • Pull revelations: Help triggered when user would benefit from that information at that moment
  • Push tutorials: Upfront product tours that users skip or forget

Nielsen Norman Group (February 2024): [Research:62] Progressive and contextual delivery introduces actions, steps, and features gradually as they become relevant to avoid cognitive overload.

Best practices: [Research:63]

  • Tooltips: Concise, context-sensitive pop-ups that give information without interrupting flow
  • Contextual hints: Explain features only when first used
  • Progressive guidance: Interactive tutorials, tooltips, step-by-step guides
  • AI-enhanced personalization: Tailor onboarding to each user’s goals and behavior [Research:62]

Implementation tools: [Research:62] Pendo, Appcues, and Userpilot enable contextual help implementation without coding.

Design principle: [Research:63] Tooltips provide subtle, contextual guidance; pop-ups or modals are more disruptive—choose appropriately.

6. Feature Discovery for Power Users

Research backing: [Research:61] [Research:64]

The challenge: Power users need to find advanced features without overwhelming beginners.

Discovery patterns:

Pattern A: Keyboard shortcuts

  • Display hints in tooltips (e.g., “⌘K to search”)
  • Provide shortcuts panel (? key)
  • Allow customization for power users

Pattern B: Search within settings

  • Filter settings by keyword
  • Jump to specific configurations
  • Avoids endless scrolling

Pattern C: “Advanced” toggle or mode

  • Switch between simplified and full interfaces
  • Remember user preference
  • Clearly labeled entry point

Pattern D: Contextual upsells

  • “Looking for more options? Try Advanced Mode”
  • Shown after user demonstrates proficiency
  • Not intrusive to beginners

2024-2025 trends: [Research:64]

  • “Choose your own UX adventure” settings for power users
  • Smart defaults with behavior-based interfaces
  • Dashboards that feel personalized
  • Adaptive interfaces: Dark mode at night, simplified UI when fatigued

7. Minimize Cognitive Load

Research backing: [Research:57] [Research:65]

Core insight: Reduce cognitive load by simplifying visual information, creating clear interaction design, and applying psychological principles. [Research:65]

Proven techniques: [Research:65]

Progressive disclosure: Accordions, tooltips, or step-by-step flows reveal content only when needed, keeping interface clean.

Simplification strategies:

  • Consistent patterns across interface
  • Clear layouts with minimal distractions
  • Group related items logically
  • Improve navigation with clear menus and visual hierarchies

Mobile-specific: Bottom navigation bars and gesture-based controls streamline user journeys. [Research:65]

Measurable impact: [Research:57]

  • Airbnb: Higher conversion, 25% fewer interactions with smart defaults
  • Figma: 24% reduction in cognitive load with minimal layouts
  • Adobe (2024): 30% increase in engagement with decluttered designs

Key principle: [Research:61] Great UX isn’t about hiding features—it’s about organizing them so users can find, understand, and use without frustration.


Nostr-Specific Considerations

Challenge 1: Relay Management Complexity

The problem: Nostr’s multi-relay architecture is powerful but overwhelming for new users.

Current failures:

  • Apps showing 50+ relay pickers during onboarding
  • [Data:31] Users setting relay preferences in one client (Nostrudel) find other clients (Coracle, Nostter) pulling relay lists incorrectly
  • [Data:31] Manual relay additions multiply unexpectedly, causing confusion
  • Read/write relay splits exposed to beginners who don’t understand the concept
  • No explanation of what relays are or why they matter

NIP-65 (Relay List Metadata) challenges: [Data:32]

  • Uses kind:10002 events to advertise user’s write relays (OUTBOX) and read relays (INBOX)
  • Clients should guide users to keep lists small (2-4 relays) [Data:32]
  • Users switching clients experience relay list confusion due to incomplete NIP implementations [Data:31]

What 80% of users need:

  • App works perfectly with zero relay configuration
  • Smart default relays chosen based on:
    • Geographic location (latency)
    • Reliability metrics
    • Community recommendations
  • No relay UI during onboarding

What 20% of power users need:

  • Relay health indicators (uptime, latency, event coverage)
  • Custom relay management
  • Read/write relay separation
  • Relay analytics and debugging tools

Progressive disclosure approach:

Level 1: Invisible (Default)

No relay UI at all. App chooses optimal relays automatically:
- 2-3 well-connected, reliable relays
- Geographic proximity for low latency
- Automatic failover if relay goes down

Level 2: Basic (Settings → Advanced)

"Network Settings" (not "Relay Management")
- [✓] Automatic relay selection (recommended)
- [ ] Custom relay configuration

Tooltip: "Nostr uses multiple servers (relays) to store your posts.
Automatic mode works great for most people."

Level 3: Power User (Settings → Advanced → Relay Management)

Full relay management interface:
- Add/remove relays manually
- Read/write relay separation
- Relay health monitoring
- NIP-65 outbox model controls
- Import/export relay lists

Implementation example:

// Smart default relays based on user location
function getDefaultRelays(userLocation: 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']
  }

  // Combine 2 global + 1-2 regional for optimal coverage
  return [...globalRelays.slice(0, 2), ...regionalRelays[userLocation].slice(0, 2)]
}

// Only show relay UI if user explicitly requests it
function shouldShowRelayUI(user: User): boolean {
  return user.settings.advancedMode === true || user.relayOverrides.length > 0
}

Challenge 2: Key Signer Apps

The problem: Remote signers enhance security but add significant complexity.

Current state: [Data:33]

  • NIP-46 specification unclear and constantly changing
  • Incompatibilities between apps and signers
  • No signer apps in traditional app stores (requires Obtanium or zap.store)
  • Clients don’t handle latency well (need better loading states, debouncing, optimistic updates)
  • Push notification support incomplete

Available signers: [Data:34]

  • Amber (Android): Offline signing, multiple accounts, granular permissions
  • Nostr Signer (Alby): Remote signing via push notifications

User experience challenges: [Data:33] While NIP-46 is best practice for key security, “it doesn’t currently work very well at all” due to spec instability and poor UX.

What 80% of users need:

  • Simple, secure key storage in-app
  • Optional passphrase/biometric protection
  • Clear backup instructions

What 20% of power users need:

  • Remote signer integration (NIP-46)
  • Hardware wallet support
  • Multiple key/identity management
  • Granular permission controls

Progressive disclosure approach:

Level 1: Simple & Secure (Default)

Onboarding:
"Your account is secured with a private key, safely stored on your device"
- Biometric unlock (Face ID/Touch ID)
- Optional passphrase
- Clear backup flow (12-word seed phrase or encrypted file)

NO mention of:
- nsec/npub distinction
- Remote signers
- NIP-46
- Cryptographic details

Level 2: Backup Awareness (After 1 week or first post)

Non-intrusive reminder:
"💡 Secure your account with a backup"
[Set up backup] [Later]

Backup flow:
"Save your recovery phrase. You'll need it if you switch devices."
[Show 12 words] → [Confirm] → [✓ Backed up]

Level 3: Advanced Security (Settings → Security → Advanced)

"Advanced Key Management"
- [ ] Use remote signer app (NIP-46)
  ℹ️ Store keys in a separate app for extra security
  [Connect Signer App]

- [ ] Export private key (nsec)
  ⚠️ Only do this if you know what you're doing
  [Show Private Key]

When to introduce signer apps:

  • NEVER during onboarding
  • Only in advanced security settings
  • With clear explanation of benefits
  • After user has successfully used app for 1+ week

Challenge 3: Protocol Terminology in UI

The problem: Nostr-specific jargon (NIPs, relays, events, kinds) exposed in user-facing UI.

Current failures:

  • Error messages: “Failed to publish kind:1 event to relay”
  • Settings: “Enable NIP-65 Outbox Model”
  • Features: “Import NIP-05 identifier”
  • Help text: “This is defined in NIP-46”

Translation guide for user-facing UI:

Technical TermUser-Facing LabelContext
RelayServer, Network“Your posts are stored on multiple servers”
EventPost, Message, Update“Your post was published successfully”
Kind:1PostNever mention kind numbers
Kind:3Following list“Your following list”
NIP-05Username, Verified name“Get a username like alice@example.com
NIP-46Remote signer“Use a separate app to manage your keys”
NIP-65Network preferences“Choose your preferred servers”
nsec/npubPrivate key/Public keyOnly in advanced settings
Outbox model(no direct equivalent)Feature should be invisible

Pattern: Hide protocol, show benefit

Bad:

⚠️ Failed to publish kind:1 event to wss://relay.example.com
NIP-01 signature verification failed

Good:

⚠️ Couldn't post right now
We'll try again automatically, or you can retry
[Retry] [Cancel]

Bad:

Settings
☐ Enable NIP-65 Outbox Model
☐ Verify NIP-01 Signatures
☐ Support NIP-46 Remote Signers

Good:

Settings
☐ Optimize network performance (recommended)
☐ Verify post authenticity (recommended)

Advanced →
  ☐ Use external key manager

Challenge 4: Advanced Features

The progressive rollout:

Week 1: Core social experience

  • Post, read, like, reply
  • Follow/unfollow
  • Profile editing
  • Notifications
  • Zero Nostr-specific concepts

Week 2-4: Light customization

  • Mute/block users (no “Web of Trust” jargon)
  • Notification preferences
  • Display preferences
  • Content filters

Month 2+: Power user features (opt-in)

  • Network settings (relay management)
  • Advanced security (remote signers)
  • Data portability (export/import)
  • Custom feeds
  • Zap configuration

Power user detection signals:

  • User explores settings multiple times
  • User asks support about advanced features
  • User follows >100 accounts
  • User posts >10 times/week
  • User has been active >30 days

Contextual feature discovery:

// Show power user hints based on behavior
if (user.daysActive > 30 && user.postsCount > 50) {
  showContextualHint(
    "💡 Did you know?",
    "You can optimize your network settings for better performance",
    "Settings → Advanced → Network"
  )
}

// Only show once, never intrusive

Challenge 5: Settings Organization

Applying 10-15 items limit to Nostr settings: [Research:58]

Basic Settings (Visible to all, <10 items)

Profile & Account
  • Edit profile
  • Username (NIP-05)
  • Privacy settings

Notifications
  • Enable notifications
  • Sound & badges

Display
  • Theme (Light/Dark/Auto)
  • Text size

Security
  • Require biometric unlock
  • Backup account

Advanced Settings (Collapsed by default)

Advanced →
  Network
    • Network optimization (auto/manual)
    • Connection status

  Security
    • Key management
    • Remote signer setup

  Data & Storage
    • Cache management
    • Import/export

  Experimental
    • Beta features
    • Debug mode

Developer/Debug (Hidden, requires code/gesture)

Hold settings icon for 3 seconds →

Developer Options
  • Relay management (full control)
  • Event inspector
  • NIP feature flags
  • Performance monitoring
  • Protocol diagnostics

Pattern Library: Concrete Solutions

Pattern A: Collapsible Advanced Settings

Problem: Settings page with 30+ options overwhelming users.

Solution: Group related settings, collapse advanced sections by default.

Implementation:

function SettingsScreen() {
  const [showAdvanced, setShowAdvanced] = useState(false)
  const [showDeveloper, setShowDeveloper] = useState(false)

  return (
    <div className="settings">
      {/* Basic Settings - Always Visible */}
      <Section title="Profile & Account">
        <Setting label="Edit profile" onClick={editProfile} />
        <Setting label="Username" value={user.nip05} />
        <Setting label="Privacy settings" onClick={showPrivacy} />
      </Section>

      <Section title="Notifications">
        <Toggle label="Enable notifications" checked={notificationsEnabled} />
        <Toggle label="Sound & badges" checked={soundEnabled} />
      </Section>

      <Section title="Display">
        <Select label="Theme" options={['Light', 'Dark', 'Auto']} value={theme} />
        <Slider label="Text size" value={textSize} min={12} max={24} />
      </Section>

      <Section title="Security">
        <Toggle label="Require biometric unlock" checked={biometricEnabled} />
        <Setting label="Backup account" onClick={showBackup} />
      </Section>

      {/* Advanced Settings - Collapsed by Default */}
      <CollapsibleSection
        title="Advanced"
        isOpen={showAdvanced}
        onToggle={() => setShowAdvanced(!showAdvanced)}
        itemCount={8}
      >
        <Subsection title="Network">
          <Toggle label="Network optimization" checked={autoNetwork} />
          <Setting label="Connection status" onClick={showNetworkStatus} />
        </Subsection>

        <Subsection title="Security">
          <Setting label="Key management" onClick={showKeyManagement} />
          <Setting label="Remote signer setup" onClick={showSignerSetup} />
        </Subsection>

        <Subsection title="Data & Storage">
          <Setting label="Cache management" onClick={showCacheSettings} />
          <Setting label="Import/export" onClick={showDataPortability} />
        </Subsection>

        <Subsection title="Experimental">
          <Toggle label="Beta features" checked={betaEnabled} />
          <Setting label="Debug mode" onClick={enableDebugMode} />
        </Subsection>
      </CollapsibleSection>

      {/* Developer Settings - Hidden */}
      {showDeveloper && (
        <CollapsibleSection title="Developer Options" isDangerous>
          <Setting label="Relay management" onClick={showRelayManager} />
          <Setting label="Event inspector" onClick={showEventInspector} />
          <Setting label="NIP feature flags" onClick={showNIPFlags} />
        </CollapsibleSection>
      )}
    </div>
  )
}

// Collapsible section component
function CollapsibleSection({ title, isOpen, onToggle, itemCount, isDangerous, children }) {
  return (
    <div className={`collapsible-section ${isDangerous ? 'dangerous' : ''}`}>
      <button onClick={onToggle} className="section-header">
        <span>{title}</span>
        {!isOpen && itemCount && <span className="badge">{itemCount}</span>}
        <ChevronIcon direction={isOpen ? 'up' : 'down'} />
      </button>
      {isOpen && <div className="section-content">{children}</div>}
    </div>
  )
}

Validation:

  • Basic settings: 8 items (under 10 limit)
  • Advanced settings: 8 items (collapsed by default)
  • Clear visual hierarchy
  • Item count badge shows what’s hidden

Pattern B: Contextual Feature Introduction

Problem: Users don’t know about advanced features that could help them.

Solution: Show contextual hints based on user behavior, never intrusive.

Implementation:

// Track user behavior to detect power user patterns
interface UserBehaviorTracker {
  daysActive: number
  postsCount: number
  followingCount: number
  settingsVisits: number
  hasSeenHint: Set<string>
}

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<ContextualHint | null>(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 (
    <div className="contextual-hint">
      <div className="hint-content">
        <h4>{hint.title}</h4>
        <p>{hint.message}</p>
      </div>
      <div className="hint-actions">
        <button onClick={hint.action.onClick} className="primary">
          {hint.action.label}
        </button>
        <button onClick={() => onDismiss(hint.id)} className="secondary">
          Dismiss
        </button>
      </div>
    </div>
  )
}

Pattern C: Progressive Relay Configuration

Problem: Relay selection too complex for beginners, but essential for power users.

Solution: Three-tier progressive disclosure (invisible → basic → advanced).

Implementation:

// Level 1: Automatic relay selection (invisible to user)
class AutomaticRelayManager {
  private userLocation: string
  private relayStats: Map<string, RelayStats>

  async getOptimalRelays(): Promise<string[]> {
    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<RelayTestResult[]> {
    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)
  }
}

// Level 2: Basic toggle (Settings → Advanced)
function NetworkSettings() {
  const [mode, setMode] = useState<'auto' | 'custom'>('auto')
  const [customRelays, setCustomRelays] = useState<string[]>([])

  return (
    <div>
      <h2>Network Settings</h2>

      <RadioGroup value={mode} onChange={setMode}>
        <Radio value="auto">
          <strong>Automatic (Recommended)</strong>
          <p>We'll choose the best servers for you</p>
          <Tooltip>
            Nostr uses multiple servers (relays) to store your posts.
            Automatic mode works great for most people.
          </Tooltip>
        </Radio>

        <Radio value="custom">
          <strong>Custom configuration</strong>
          <p>Manually choose your servers</p>
        </Radio>
      </RadioGroup>

      {mode === 'custom' && (
        <button onClick={() => navigate('/settings/advanced/relays')}>
          Manage relays 
        </button>
      )}
    </div>
  )
}

// Level 3: Full relay management (Settings → Advanced → Relay Management)
function RelayManagementScreen() {
  const [relays, setRelays] = useState<Relay[]>([])
  const [showAddRelay, setShowAddRelay] = useState(false)

  return (
    <div className="relay-manager">
      <h2>Relay Management</h2>
      <p className="description">
        Advanced users: Customize which servers store and relay your posts
      </p>

      {/* Relay List */}
      <div className="relay-list">
        {relays.map(relay => (
          <RelayCard
            key={relay.url}
            relay={relay}
            onRemove={() => removeRelay(relay.url)}
            onToggleRead={() => toggleRelayRead(relay.url)}
            onToggleWrite={() => toggleRelayWrite(relay.url)}
          />
        ))}
      </div>

      {/* Add Relay */}
      <button onClick={() => setShowAddRelay(true)}>
        + Add relay
      </button>

      {/* Advanced Options */}
      <CollapsibleSection title="Advanced Options">
        <Toggle
          label="Enable NIP-65 Outbox Model"
          tooltip="Use your relay preferences when others fetch your posts"
          checked={nip65Enabled}
          onChange={setNip65Enabled}
        />
        <Setting
          label="Import relay list"
          onClick={importRelayList}
        />
        <Setting
          label="Export relay list"
          onClick={exportRelayList}
        />
      </CollapsibleSection>

      {/* Relay Health Monitoring */}
      <Section title="Relay Health">
        <RelayHealthDashboard relays={relays} />
      </Section>
    </div>
  )
}

function RelayCard({ relay, onRemove, onToggleRead, onToggleWrite }) {
  return (
    <div className="relay-card">
      <div className="relay-info">
        <StatusIndicator status={relay.status} />
        <span className="relay-url">{relay.url}</span>
        <span className="relay-latency">{relay.latency}ms</span>
      </div>

      <div className="relay-controls">
        <Toggle
          label="Read"
          size="small"
          checked={relay.read}
          onChange={onToggleRead}
        />
        <Toggle
          label="Write"
          size="small"
          checked={relay.write}
          onChange={onToggleWrite}
        />
        <button onClick={onRemove} className="icon-button">
          <TrashIcon />
        </button>
      </div>
    </div>
  )
}

Pattern D: Smart Feature Gating

Problem: Advanced features visible to all users, causing confusion.

Solution: Gate features based on user proficiency level, with clear upgrade path.

Implementation:

// User proficiency detection
enum UserLevel {
  BEGINNER = 'beginner',      // 0-7 days, <10 posts
  INTERMEDIATE = 'intermediate', // 7-30 days, 10-50 posts
  ADVANCED = 'advanced',        // 30+ days, 50+ posts
  POWER_USER = 'power_user'     // Explicitly enabled advanced mode
}

function getUserLevel(user: User): UserLevel {
  // Explicit power user mode
  if (user.settings.advancedMode) {
    return UserLevel.POWER_USER
  }

  // Based on activity
  const { daysActive, postsCount, followingCount } = user.stats

  if (daysActive > 30 && postsCount > 50) {
    return UserLevel.ADVANCED
  }

  if (daysActive > 7 && postsCount > 10) {
    return UserLevel.INTERMEDIATE
  }

  return UserLevel.BEGINNER
}

// Feature gating component
function FeatureGate({
  requiredLevel,
  feature,
  fallback,
  children
}: {
  requiredLevel: UserLevel
  feature: string
  fallback?: React.ReactNode
  children: React.ReactNode
}) {
  const user = useUser()
  const userLevel = getUserLevel(user)

  const levels = [
    UserLevel.BEGINNER,
    UserLevel.INTERMEDIATE,
    UserLevel.ADVANCED,
    UserLevel.POWER_USER
  ]

  const hasAccess = levels.indexOf(userLevel) >= levels.indexOf(requiredLevel)

  if (hasAccess) {
    return <>{children}</>
  }

  // Show upgrade prompt for intermediate/advanced users
  if (userLevel >= UserLevel.INTERMEDIATE && fallback) {
    return <>{fallback}</>
  }

  // Hide completely for beginners
  return null
}

// Usage examples
function ComposerToolbar() {
  return (
    <div className="composer-toolbar">
      {/* Always visible */}
      <button>Bold</button>
      <button>Italic</button>
      <button>Link</button>

      {/* Intermediate+ */}
      <FeatureGate requiredLevel={UserLevel.INTERMEDIATE} feature="markdown">
        <button>Code</button>
        <button>Quote</button>
      </FeatureGate>

      {/* Advanced+ */}
      <FeatureGate
        requiredLevel={UserLevel.ADVANCED}
        feature="custom-relays"
        fallback={
          <Tooltip>
            Unlock custom relay selection after 30 days of activity
          </Tooltip>
        }
      >
        <button onClick={openRelaySelector}>
          Choose relays for this post
        </button>
      </FeatureGate>

      {/* Power users only */}
      <FeatureGate requiredLevel={UserLevel.POWER_USER} feature="raw-event">
        <button onClick={editRawEvent}>Edit raw event</button>
      </FeatureGate>
    </div>
  )
}

function SettingsMenu() {
  const userLevel = getUserLevel(useUser())

  return (
    <div>
      {/* Basic settings - always visible */}
      <MenuItem to="/settings/profile">Profile</MenuItem>
      <MenuItem to="/settings/notifications">Notifications</MenuItem>
      <MenuItem to="/settings/display">Display</MenuItem>

      {/* Advanced settings - gated */}
      <FeatureGate requiredLevel={UserLevel.INTERMEDIATE} feature="advanced-settings">
        <MenuItem to="/settings/advanced">Advanced</MenuItem>
      </FeatureGate>

      {/* Power user prompt */}
      {userLevel === UserLevel.ADVANCED && (
        <MenuItem onClick={enablePowerUserMode}>
          <span>Enable Power User Mode</span>
          <Badge>Unlock all features</Badge>
        </MenuItem>
      )}
    </div>
  )
}

Pattern E: In-App Feature Education

Problem: Users don’t understand what advanced features do or when to use them.

Solution: Contextual tooltips and progressive education.

Implementation:

// 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 (
    <Tooltip
      isOpen={isOpen}
      onOpenChange={setIsOpen}
      content={
        <div className="educational-tooltip">
          <h4>{title}</h4>
          <p>{description}</p>
          <div className="tooltip-actions">
            {learnMoreUrl && (
              <a href={learnMoreUrl} target="_blank">
                Learn more 
              </a>
            )}
            <button onClick={markAsSeen}>Got it</button>
          </div>
        </div>
      }
      badge={!hasSeenTooltip ? '!' : undefined}
    >
      {children}
    </Tooltip>
  )
}

// Usage in advanced settings
function AdvancedNetworkSettings() {
  return (
    <div>
      <EducationalTooltip
        feature="relay-read-write-split"
        title="Read/Write Relay Split"
        description="Choose different servers for reading posts (read) and publishing your posts (write). This can improve performance and privacy."
      >
        <Toggle
          label="Separate read and write relays"
          checked={splitRelays}
          onChange={setSplitRelays}
        />
      </EducationalTooltip>

      <EducationalTooltip
        feature="outbox-model"
        title="Outbox Model (NIP-65)"
        description="Tell other users which servers to check for your posts. This helps your posts reach more people reliably."
        learnMoreUrl="https://github.com/nostr-protocol/nips/blob/master/65.md"
      >
        <Toggle
          label="Enable outbox model"
          checked={outboxEnabled}
          onChange={setOutboxEnabled}
        />
      </EducationalTooltip>

      <EducationalTooltip
        feature="relay-auth"
        title="Relay Authentication"
        description="Some relays require authentication to prevent spam. This lets you connect to authenticated relays."
      >
        <Toggle
          label="Allow relay authentication (NIP-42)"
          checked={authEnabled}
          onChange={setAuthEnabled}
        />
      </EducationalTooltip>
    </div>
  )
}

Anti-Patterns: What Not To Do

Anti-Pattern 1: Relay Picker in Onboarding

What it looks like:

Welcome to Nostr!
Step 1: Choose your relays (required)

[ ] wss://relay.damus.io
[ ] wss://nos.lol
[ ] wss://relay.nostr.band
... (47 more relays)

ℹ️ Relays are servers that store your events. Choose at least 3.
Learn more about NIP-01 relay specification →

[Continue]

Why it fails:

  • New users have no idea what relays are
  • Overwhelming choice before understanding the platform
  • Technical jargon (“events”, “NIP-01”) scares users away
  • Creates decision paralysis
  • Users abandon before reaching value

What to do instead:

  • Zero relay configuration during onboarding
  • App chooses optimal relays automatically (2-3 well-connected ones)
  • Defer relay management to advanced settings
  • Only show to power users who request it

Good example:

Welcome to Nostr!

[Create account]  [Sign in]

(No relay selection---handled automatically)

Anti-Pattern 2: NIP Jargon in User-Facing UI

What it looks like:

Settings
☑ Enable NIP-01 Event Verification
☑ Support NIP-05 Identifiers
☑ Use NIP-46 Remote Signers
☑ Implement NIP-65 Outbox Model
☐ Enable NIP-42 Relay Authentication

⚠️ Failed to publish kind:1 event to wss://relay.example.com
NIP-01 signature verification failed on event:
a3f82...

Why it fails:

  • Users don’t know what NIPs are (nor should they)
  • Technical protocol references confuse mainstream users
  • Error messages expose implementation details
  • Makes app feel like developer tool, not consumer product

What to do instead:

  • Translate protocol terms to user benefits
  • Hide NIP numbers completely
  • Use human-readable error messages
  • Reference NIPs only in developer documentation

Good example:

Settings
☑ Verify post authenticity (recommended)
☑ Get a username
☑ Use external key manager
☑ Optimize network performance
☐ Connect to private servers

⚠️ Couldn't post right now
We'll try again automatically
[Retry] [Cancel]

Anti-Pattern 3: All Features Visible

What it looks like:

Composer Toolbar:
[B] [I] [U] [Link] [Image] [Video] [Poll] [Article]
[Code] [Quote] [Spoiler] [Table] [Embed] [LaTeX]
[Choose Relays] [Set Expiration] [Edit Raw Event]
[Add Hashtags] [Mention] [Emoji] [GIF] [Location]

Why it fails:

  • 20+ buttons overwhelming beginners
  • Most users only need 3-4 core features
  • Power features clutter interface
  • Paradox of choice: more options = less usage

What to do instead:

  • Show 5-7 core features by default
  • Progressive disclosure for advanced features
  • Feature gating based on user level
  • Overflow menu for less-used actions

Good example:

Composer Toolbar:
[B] [I] [Link] [Image] [⋯ More]

More menu (contextual):
→ Code block
→ Quote
→ Poll
→ Advanced options (power users only)

Anti-Pattern 4: No Smart Defaults

What it looks like:

First Launch
Before you can use this app, please configure:

1. Select your relays (0 selected)
2. Choose read/write relay split
3. Set up relay authentication
4. Configure event cache duration
5. Set network optimization preferences
6. Choose signature verification level

[Start Configuration]

Why it fails:

  • Requires decisions before user understands platform
  • No “just works” experience
  • Users abandon rather than configure
  • Assumes technical knowledge

What to do instead:

  • App works perfectly with zero configuration
  • Smart defaults for 80% of users
  • Defer customization to advanced settings
  • One-click “Optimize automatically” option

Good example:

Welcome to Nostr!

[Create account]  [Sign in]

(Everything configured automatically)

Anti-Pattern 5: Settings Overload

What it looks like:

Settings (67 items on one screen)

Profile
Notifications
Display
Privacy
Security
Network
  - Relay 1 URL
  - Relay 1 Read/Write
  - Relay 2 URL
  - Relay 2 Read/Write
  ... (10 relays, 20 settings)
Performance
Cache
Storage
Advanced
Developer
Experimental
...
(endless scrolling)

Why it fails:

  • Overwhelming cognitive load
  • Violates 10-15 items limit
  • Users can’t find what they need
  • Everything looks equally important
  • Poor visual hierarchy

What to do instead:

  • Basic settings: <10 items
  • Group related settings logically
  • Collapse advanced sections by default
  • Use search for finding settings
  • Hide developer options completely

Good example:

Settings

Profile & Account
Notifications
Display
Security

Advanced ▼ (8 items)

(Developer options: hidden)

Anti-Pattern 6: Upfront Tutorial Overload

What it looks like:

Welcome to Nostr! Let's get you started.

Tutorial (12 steps):
1. What are relays?
2. Understanding events and kinds
3. Public and private keys (nsec/npub)
4. NIPs and protocol extensions
5. How to choose relays
6. Read vs Write relays
7. Outbox model (NIP-65)
8. Relay authentication (NIP-42)
9. Remote signers (NIP-46)
10. Web of Trust
11. Zaps and Lightning
12. Data portability

[Start Tutorial] [Skip]

(Users hit Skip, never learn anything)

Why it fails:

  • Information overload before usage
  • Users skip and never return
  • Teaches protocol, not usage
  • No context for why it matters

What to do instead:

  • Contextual help when features are encountered
  • “Pull” education (user-triggered) not “push” (forced)
  • Short, focused tooltips at point of use
  • Progressive education as users grow

Good example:

Welcome to Nostr!

[Create account]  [Sign in]

(No upfront tutorial)

Later, when user first encounters relay settings:
💡 Servers (Relays)
Nostr uses multiple servers to store posts.
We've chosen the best ones for you.
[Got it]

Anti-Pattern 7: Power Features in Main Nav

What it looks like:

Main Navigation:
🏠 Home
🔔 Notifications
💬 Messages
👤 Profile
🔧 Relay Manager
📊 Event Inspector
🔑 Key Management
⚙️ NIP Feature Flags
🐛 Debug Console

Why it fails:

  • Developer tools mixed with user features
  • Beginners confused by technical options
  • Poor information architecture
  • Visual clutter

What to do instead:

  • Main navigation: core user features only
  • Advanced features in settings
  • Developer tools hidden (gesture/code to unlock)
  • Clear separation of concerns

Good example:

Main Navigation:
🏠 Home
🔔 Notifications
💬 Messages
👤 Profile
⚙️ Settings
  → Advanced
      → Developer Options (if enabled)

Anti-Pattern 8: Immediate Signer Requirement

What it looks like:

Create Account

For maximum security, you need a remote signer app.

Step 1: Install Amber (Android) or Nostr Signer (iOS)
Step 2: Generate keys in signer app
Step 3: Connect this app to your signer
Step 4: Approve connection via NIP-46

[Install Signer App]

Note: Signer apps are not available in app stores.
You'll need to install Obtanium first...

Why it fails:

  • Massive friction before value
  • Requires installing 2-3 apps before using one
  • Signer apps not in official stores
  • NIP-46 has poor UX and compatibility issues
  • 99% of users will abandon

What to do instead:

  • Simple in-app key storage by default
  • Optional passphrase/biometric protection
  • Introduce signer apps AFTER user is active (1+ weeks)
  • Clear explanation of benefits
  • Make it optional, not required

Good example:

Create Account

Your account is secured with a private key,
safely stored on your device.

[Create account]

Later (after 1 week):
💡 Extra Security
Want even more protection? Use a dedicated
signer app to manage your keys.
[Learn more] [Not now]

Validation Checklist

Feature Usage Metrics

Track which features are actually used:

  • Feature usage by user level:
    • Beginner (0-7 days): Which features do they use?
    • Intermediate (7-30 days): New features adopted?
    • Advanced (30+ days): Power features engaged?
  • Feature adoption rates:
    • What % of users ever use advanced features?
    • What % use them regularly (>1x/week)?
  • Settings engagement:
    • How many users visit advanced settings?
    • Which settings are changed most often?
    • Which settings are never touched?

Questions to answer:

  • Are <20% of features used by >80% of users? (Pareto principle validation)
  • Which advanced features can be hidden without impact?
  • Which “basic” features should be demoted?

Settings Complexity Audit

Count settings by tier:

  • Basic settings (visible to all): Must be <10 items
  • Advanced settings (collapsed): Should be <15 items
  • Expert/Developer settings: Hidden entirely

Validate hierarchy:

  • Frequently used settings at top
  • Related settings grouped logically
  • No more than 3 levels of nesting
  • Each setting has clear, jargon-free label

User testing:

  • New users can find basic settings in <10 seconds
  • Users don’t feel overwhelmed by settings screen
  • Power users can find advanced features when needed

Progressive Disclosure Effectiveness

Measure cognitive load:

  • Onboarding completion rate: Target >70%
  • Time to first value: <2 minutes for new users
  • Decision points during setup: <3 required decisions

User feedback:

  • “Does the app feel overwhelming?” (Target: <10% say yes)
  • “Can you find what you need?” (Target: >80% say yes)
  • “Do you feel in control?” (Target: >70% say yes)

A/B test progressive disclosure:

  • Test A: All features visible
  • Test B: Progressive disclosure
  • Measure: Completion rate, time-to-value, feature discovery

Nostr-Specific Validation

Relay management:

  • Default relay selection works for 80% of users:
    • Post success rate >95%
    • Average latency <2s
    • No manual intervention needed
  • Advanced users can find relay management:
    • <20% of users access it
    • Those who do understand it (support tickets as proxy)
    • Power users satisfied with control level

Protocol terminology:

  • Zero NIP references in basic UI:
    • Error messages use plain language
    • Settings use benefits, not protocol terms
    • Help documentation explains concepts simply
  • User comprehension:
    • Users understand what “servers” mean (not “relays”)
    • Users understand “username” (not “NIP-05”)
    • Users understand “backup” (not “nsec export”)

Signer app introduction:

  • Never required during onboarding: 100% compliance
  • Introduced after 1+ week: For active users only
  • Adoption rate tracked:
    • What % of eligible users (1+ week) set up signer?
    • What % abandon during signer setup?
    • What % successfully complete setup?

User Research Questions

For beginners (0-7 days):

  • “Did anything confuse you during setup?”
  • “What features do you wish were simpler?”
  • “What features do you wish were easier to find?”
  • “Did you see any technical terms you didn’t understand?”

For intermediate users (7-30 days):

  • “Have you explored advanced settings?”
  • “What features did you discover after using the app for a while?”
  • “What would you change about the settings organization?”

For advanced users (30+ days):

  • “Can you find all the power user features you need?”
  • “What advanced features are missing?”
  • “Would you prefer more or less control over technical settings?”

Success Metrics

Primary metrics:

  • Onboarding completion rate: >70% (vs. current baseline)
  • D1/D7/D30 retention: Improved by >10%
  • Time to first post: <2 minutes (down from 15-20 minutes)
  • “Too complicated” complaints: <10% of users

Secondary metrics:

  • Advanced settings usage: 10-20% of users
  • Support tickets for relay issues: Down >50%
  • Users switching clients for features: Down >30%
  • Power user satisfaction: >80% can find what they need

Comparative benchmarks:

  • Onboarding simpler than Twitter/Instagram? (user survey)
  • Settings organization clearer than competitors? (user survey)
  • Advanced features as discoverable as Figma/Slack? (power user survey)

A/B Testing Opportunities

Test 1: Relay visibility

  • A: Relays in onboarding
  • B: Relays hidden entirely
  • Measure: Completion rate, confusion, post success

Test 2: Settings organization

  • A: Flat list (all settings visible)
  • B: Collapsed advanced section
  • Measure: Time to find settings, perceived complexity

Test 3: Feature gating

  • A: All features always visible
  • B: Progressive feature unlock (beginner → advanced)
  • Measure: Feature discovery, usage rates, satisfaction

Test 4: Terminology

  • A: Protocol terms (relays, NIPs, events)
  • B: User-friendly terms (servers, username, posts)
  • Measure: Comprehension, support tickets, confusion

Test 5: Signer introduction

  • A: Signer during onboarding (current some apps)
  • B: Signer after 1 week (recommended)
  • Measure: Completion rate, signer adoption, abandonment

Citations & Sources

Note: All sources from 2024-2025 to ensure currency for this fast-moving technology.

Universal UX Research

  • [Research:54] Pareto Principle (80/20): 80% users use only 20% of features; focus on 20% of decisions driving 80% impact (September 2025)
  • [Research:55] Progressive Disclosure: 30-40% cognitive load reduction during onboarding; breaks complexity into manageable steps (May 2025)
  • [Research:56] Progressive Disclosure Strategy: Reduces decision fatigue via staged revelation; collapsible menus, tooltips, toggles (2024)
  • [Research:57] Cognitive Load Impact: Airbnb 25% fewer interactions, Figma 24% load reduction, Adobe 30% engagement increase (April 2025)
  • [Research:58] Android Settings: Limit to 10-15 items/screen; frequently used at top; users should glance and understand (2024)
  • [Research:59] Pareto in Interface Design: Identify 20% features meeting 80% needs; MVP development is Pareto in action (2024)
  • [Research:60] Progressive Disclosure Techniques (NN/g): Defer advanced to secondary UI; use for novices, complex tasks, limited space (2024)
  • [Research:61] Complexity Organization: Great UX isn’t hiding features—it’s organizing them; visual hierarchy prioritizes user needs (May 2025)
  • [Research:62] Contextual Help (NN/g): Pull revelations when beneficial; progressive delivery; AI-enhanced personalization (February 2024)
  • [Research:63] Tooltip Design: Subtle contextual guidance vs disruptive modals; concise, context-sensitive, non-interrupting (2024)
  • [Research:64] UX Trends for Power Users: “Choose your own UX adventure”; smart defaults, adaptive interfaces (dark mode, simplified) (2025)
  • [Research:65] Cognitive Load Techniques: Progressive disclosure (accordions, tooltips), consistent patterns, bottom nav, gestures (2024)

Nostr-Specific Data

  • [Data:31] Cross-Client Relay Confusion: Nostrudel settings don’t transfer to Coracle/Nostter; “imperfect NIP implementations” (2024)
  • [Data:32] NIP-65 Relay List: kind:10002 for OUTBOX/INBOX relays; clients should guide to 2-4 relays (2024)
  • [Data:33] NIP-46 Signer UX: “Unclear and constantly changing”; incompatibilities; no app store availability; poor latency handling (2024)
  • [Data:34] Nostr Signers: Amber (Android offline, multi-account, granular); Alby (remote via push) (2024)

See References & Bibliography for full citation details.


Related Patterns


Next Steps

  1. Audit feature usage by user cohort: Validate Pareto principle (80% of users use 20% of features) and identify which advanced features can be hidden
  2. Implement progressive disclosure hierarchy: Move relay configuration, NIP settings, and technical options to advanced settings (Settings → Advanced → [Feature])
  3. Translate protocol terminology: Replace all user-facing NIP references, relay jargon, and technical terms with plain language (“servers” not “relays”, “username” not “NIP-05”)
  4. Build feature gating system: Create user proficiency detection (beginner/intermediate/advanced) and gate power features appropriately with clear upgrade paths

Last updated on