Pattern 6: Cross-Client Consistency & Data Integrity
Problem Statement
Current State
Data Loss Crisis:
- Users losing all their follows when switching between clients (Primal → Damus → Snort) [Data:14]
- Race conditions in kind:3 events causing catastrophic follow list destruction when clients sync [Data:15]
- Profile changes not appearing across all apps
- Posts disappearing from feeds when relays go offline [Data:12]
- Users forced to manually re-follow everyone after switching clients
- No visibility into what data is synced vs. out-of-sync
This is the most severe UX failure in Nostr. Users don’t trust the platform after losing data once.
The trust problem:
- [Research:66] 85% of consumers deleted a mobile app due to privacy/data concerns (2025 Digital Trust Index)
- [Research:67] Over 80% of businesses saw boost in customer loyalty after focusing on data integrity
- [Research:68] Distributed systems face “scams are everywhere” problem—UX equals trust
- Users don’t trust Nostr after losing data once
- “It just works” expectation from mainstream apps not met
The user experience impact:
- Users need 5-6 different clients to work around data sync bugs
- Constant anxiety about data loss
- Manual verification required (checking if posts/follows synced)
- Support requests: “Where did my followers go?”
- Abandonment: “I lost everything, I’m done”
Root Causes
- Multi-relay coordination without conflict resolution: Events spread across relays can conflict with no automatic resolution
- Race conditions in replaceable events: Kind:3 (follows), kind:0 (metadata) can overwrite newer data with stale data [Data:15]
- No sync state visibility: Users have zero indication of what’s synced, syncing, or failed
- Relay downtime causing silent data loss: [Data:12] [Data:17] Content on downed relays simply disappears
- Client-side caching inconsistencies: Different clients cache different subsets of relays
- No conflict detection or user notification: Data conflicts happen silently, users discover loss later
Why This Matters
Research shows: [Research:69] [Research:70] [Research:71]
- 72% of users expect instant data sync across devices (real-time expectations) [Research:70]
- 45% of users expect content to display correctly across different devices [Research:72]
- Consistency reduces cognitive load by allowing users to apply learned patterns [Research:73] [Research:74]
- Users notice sync conflicts immediately and become confused [Research:69]
- Failing to maintain consistency increases cognitive load and forces users to learn something new in each client [Research:73]
The stakes for Nostr:
“When users lose data once, they never fully trust the platform again.”
Universal Principles
These principles apply to any distributed system managing user data across multiple clients.
1. Consistency & Standards
Research backing: [Research:73] [Research:74] [Research:75]
Core principle (Nielsen Norman Group, 2024): [Research:73]
- When applications maintain consistency, users know what to expect
- Learnability is increased, confusion is reduced
- Failing to maintain consistency increases users’ cognitive load
Two types of consistency:
- Internal consistency: Within a single product or family of products
- External consistency: Following established industry conventions
Application to Nostr clients:
- Internal: User’s data should be identical across all their own client instances
- External: Follow conventions from mainstream social apps (Twitter, Instagram) for how data syncs
Material Design 3 research: [Research:75]
- Backed by 46 research studies involving 18,000+ participants worldwide
- Provides common language of components and patterns for cohesive cross-platform experiences
- Consistency creates seamless, intuitive experiences
2. Real-Time Sync Expectations
User Expectations (2024-2025): [Research:70] [Research:72] [Research:76]
- 72% of users expect instant reflection of changes across platforms
- 45% expect content to display correctly across different devices
- 69% appreciate quick reply times (59% expect <5 second response)
Real-time sync is no longer optional. Users expect to seamlessly switch between devices/clients without losing continuity.
Cross-device expectations: [Research:77]
- Delivering seamless cross-device experience is “cornerstone of digital marketing success” in 2025
- Users expect frictionless transitions between devices
- Content, state, and interactions must persist
3. Offline-First & Sync States
Research backing: [Research:78] [Research:79] [Research:80]
Modern user expectations: [Research:78]
“Users today do not wish to view a plain ‘You’re offline’ message—PWA 2.0 confirms applications function even in the absence of the internet.”
Essential UX elements: [Research:78] [Research:79]
- Offline/Online/Sync state indicators are table stakes
- Users can create/edit content offline
- Content automatically syncs when connectivity returns
- Seamless experience: Users don’t notice when they’re offline [Research:80]
Progressive Web App patterns: [Research:79]
- Apps learn usage patterns and pre-cache likely-needed content
- Offline editing queues for automatic sync
- “Offline sync isn’t optional anymore—it’s a must-have for great UX”
4. Conflict Resolution Strategies
Research backing: [Research:69] [Research:81] [Research:82]
The challenge: [Research:69]
“When multiple users modify the same record offline, conflicts occur during synchronization, causing users to become confused by sync conflicts.”
Common strategies: [Research:81] [Research:82]
- Last Write Wins (LWW): Simple timestamp-based resolution
- CRDTs (Conflict-free Replicated Data Types): For complex data structures
- Manual resolution: UI for users to choose which version to keep
- Version vectors: Track updates across nodes for conflict detection [Research:82]
Best practices: [Research:83]
- Communicate expectations to users about potential delays
- Provide clear sync status
- Allow manual reconciliation when conflicts can’t be auto-resolved
- Inform users about conflict resolution strategy in use
5. Trust Through Transparency
Research backing: [Research:67] [Research:68] [Research:66]
Trust problem: [Research:66]
- 2025 Digital Trust Index: universal decline in trust for digital services
- Not one sector reached above 50% approval for data trustworthiness
- 85% of consumers deleted mobile apps due to data privacy concerns
Trust-building strategies: [Research:68] [Research:67]
- Transparency: Real-time information about transaction progress reduces anxiety [Research:68]
- Clear explanations: Detailed, clear risk explanations before approving actions [Research:68]
- Privacy measures: 80% of businesses saw customer loyalty boost from privacy focus [Research:67]
- User control: In decentralized systems, users own all control—UX must reflect this [Research:68]
Critical for distributed systems: [Research:68]
“In an industry where scams are everywhere, UX = trust. Blockchain cannot guarantee the quality of service of DApps, which entirely depends on the services’ performance.”
6. Distributed Systems Constraints
Research backing: [Research:83] [Research:82] [Research:84]
Core challenges: [Research:83] [Research:84]
- Maintaining consistency across distributed replicas is inherently difficult
- Concurrent access without conflicts requires careful state management
- Network partitions and latency cause unavoidable delays
Design strategies: [Research:82] [Research:83]
- Set realistic expectations: Inform users about sync delays [Research:83]
- Version tracking: Use version vectors to track updates across nodes [Research:82]
- Conflict detection: Identify conflicts early before users discover data loss
- Graceful degradation: System remains usable even during network issues
State management best practices: [Research:84]
- Enable concurrent access without conflicts
- Optimize resource utilization for performance and responsiveness
- Provide scalability to handle increasing loads
- Maintain consistency across distributed replicas
Nostr-Specific Considerations
Challenge 1: Kind:3 Race Conditions
The problem: [Data:14] [Data:15]
Replaceable events (kind:3 contact lists) use “last event wins” logic, but without proper synchronization, clients can overwrite newer data with stale cached data.
Documented failures: [Data:14]
“I was trying out the new Iris Nostr client and decided to follow someone new. From that moment on, I noticed my follows count reset from about 130 to 1.”
Root cause: [Data:15]
“A very common experience on Nostr is that of losing follows due to race conditions when sending kind 3 events… Earlier this week someone signed in to Coracle, their contact list failed to fully sync before they followed someone, and they ended up deleting all their follows.”
Why it happens:
- User has 130 follows stored across relays
- User opens new client (Iris/Coracle)
- Client begins fetching kind:3 from relays (slow, async)
- Before sync completes, user follows 1 new person
- Client publishes NEW kind:3 with only that 1 follow
- Newer event (with 1 follow) replaces older event (with 130 follows)
- All follows lost
NIP-02 specification: [Protocol:2]
“Each new contact list event is a replaceable event that supersedes previous ones. Must contain ALL pubkeys the user is following, as event replaces previous list entirely.”
The solution pattern:
- Block follow/unfollow during initial sync
- Show “Syncing your follows… X of Y relays” progress indicator
- Only enable follow button once kind:3 fully loaded from majority of relays
- Client-side validation: “You have 130 follows. Are you sure you want to replace with 1?”
Code pattern:
let contactListFullySynced = false;
let contactListSyncProgress = { loaded: 0, total: relays.length };
async function syncContactList() {
const events = await Promise.all(
relays.map(r => r.fetchKind3())
);
// Merge all kind:3 events, taking the one with latest created_at
const merged = mergeContactLists(events);
contactListFullySynced = true;
enableFollowButtons();
}
function followUser(pubkey) {
if (!contactListFullySynced) {
showWarning("Still syncing your follows. Please wait...");
return;
}
// Safe to follow now
const newContactList = [...currentContacts, pubkey];
publishKind3(newContactList);
}Challenge 2: Profile Metadata
The problem: Similar to kind:3, kind:0 (profile metadata) is replaceable and suffers from the same race conditions.
User experience:
- Update profile picture in Damus
- Open Primal → old avatar shows
- Update bio in Primal
- Open Damus → new bio, but avatar reverted to old one
- Clients publishing partial kind:0 events that overwrite full profiles
Solution pattern:
- Always fetch kind:0 from all relays before allowing edits
- Merge strategy: take the most recent complete event
- Warn user if editing while kind:0 is still syncing
- Show “Last synced” timestamp for profile data
Challenge 3: Relay Downtime
The problem: [Data:12] [Data:17]
Relay infrastructure reality: [Data:17]
- Only 639 relays online globally (two-thirds reduction from previous year)
- 95% struggle to cover operational costs
- 20% have faced significant downtime
What happens when relays go down: [Data:12]
“When the Damus relay was taken down for upgrades, users’ content was potentially wiped and gone… content stored on that relay was reduced to remaining on one less relay.”
User experience:
- Post from phone → published to 5 relays
- 2 relays go offline
- Open desktop client → only connects to 3 relays still online
- Post missing from feed
- User: “Did I imagine posting this?”
Solution pattern:
- Multi-relay redundancy: Always publish to ≥5 relays
- Relay health monitoring: Track which relays are responsive
- Retry failed publishes: Queue and retry events that failed to publish
- User visibility: “Posted to 3 of 5 relays. Retrying…”
Challenge 4: NIP-65 Relay List Metadata Sync
The specification: [Protocol:5]
Kind:10002 events advertise user’s write relays (OUTBOX) and read relays (INBOX). Other clients use this to discover where to find your content and where to send you mentions.
The problem:
- User sets relay preferences in Client A
- Client B doesn’t implement NIP-65 properly
- Client C reads wrong relays from outdated kind:10002
- Messages/mentions don’t reach user
- User thinks they’re being ignored
Solution pattern:
- Always publish kind:10002 to well-known relays
- Keep relay lists small (2-4 relays as recommended)
- Validate relay list on every client launch
- Show “Your relay preferences are synced across all clients” confirmation
Pattern Library: Concrete Solutions
Pattern 1: Sync State Visibility
Problem: Users have zero visibility into what’s synced vs. out-of-sync.
Solution: Show sync status in UI with clear, actionable information.
Implementation:
// Sync state indicator component
function SyncStatusBadge({ syncState }) {
const states = {
syncing: {
icon: "⟳",
text: "Syncing...",
color: "blue",
detail: `${syncState.progress.loaded}/${syncState.progress.total} relays`
},
synced: {
icon: "✓",
text: "Synced",
color: "green",
detail: `Last synced ${formatRelativeTime(syncState.lastSync)}`
},
conflict: {
icon: "⚠",
text: "Sync conflict",
color: "orange",
detail: "Tap to resolve"
},
error: {
icon: "✗",
text: "Sync failed",
color: "red",
detail: `${syncState.failedRelays.length} relays unreachable`
},
offline: {
icon: "○",
text: "Offline",
color: "gray",
detail: "Changes will sync when online"
}
};
const state = states[syncState.status];
return (
<div className={`sync-badge sync-${state.color}`}>
<span className="sync-icon">{state.icon}</span>
<span className="sync-text">{state.text}</span>
<span className="sync-detail">{state.detail}</span>
</div>
);
}Where to show:
- Settings screen (persistent)
- Profile edit screen (while saving)
- Follow/unfollow actions (transient)
- Post composition (before publishing)
Example UI:
┌─────────────────────────────────┐
│ Profile ✓ Synced │
│ Last synced 2 mins ago │
├─────────────────────────────────┤
│ Display name: Alice │
│ Bio: Nostr developer │
│ │
│ [Save changes] │
└─────────────────────────────────┘
While saving:
┌─────────────────────────────────┐
│ Profile ⟳ Syncing... │
│ Saving to 3/5 relays │
├─────────────────────────────────┤Pattern 2: Optimistic Updates with Rollback
Problem: Users expect instant feedback, but relays may fail or conflict.
Solution: Optimistic UI that rolls back on failure.
Implementation:
async function followUser(pubkey) {
// Optimistic update
const previousState = [...currentFollows];
currentFollows.push(pubkey);
updateUI(); // User sees follow immediately
try {
const kind3Event = createKind3Event(currentFollows);
const results = await publishToRelays(kind3Event, relays);
const successfulRelays = results.filter(r => r.success);
if (successfulRelays.length < (relays.length * 0.6)) {
// Failed to publish to majority
throw new Error("Insufficient relays confirmed");
}
// Success - show confirmation
showToast(`Following ${getUsername(pubkey)}`);
} catch (error) {
// Rollback optimistic update
currentFollows = previousState;
updateUI();
// Show user-friendly error
showError(
"Couldn't follow user",
"Some relays are unreachable. Try again?",
{ retry: () => followUser(pubkey) }
);
}
}User experience:
- User clicks “Follow” → Button immediately shows “Following”
- Background: Publishing to 5 relays
- Success (3+ relays confirm) → Toast: “Following Alice”
- Failure (<3 relays) → Revert button, show error with retry option
Pattern 3: Conflict Resolution UI
Problem: When conflicts occur, users need a way to resolve them.
Solution: Clear conflict resolution interface.
Implementation:
function ConflictResolutionModal({ conflict }) {
const { localVersion, remoteVersion } = conflict;
return (
<Modal title="Sync Conflict Detected">
<p>Your {conflict.dataType} has different versions:</p>
<div className="conflict-options">
<VersionCard
label="This Device"
data={localVersion}
lastModified={localVersion.timestamp}
action={() => resolveConflict('keep-local')}
/>
<VersionCard
label="Other Devices"
data={remoteVersion}
lastModified={remoteVersion.timestamp}
action={() => resolveConflict('keep-remote')}
/>
<button onClick={() => resolveConflict('merge')}>
Merge Both (Keep All Follows)
</button>
</div>
</Modal>
);
}
function resolveConflict(strategy) {
switch (strategy) {
case 'keep-local':
publishToAllRelays(localVersion);
break;
case 'keep-remote':
updateLocalState(remoteVersion);
break;
case 'merge':
const merged = mergeVersions(localVersion, remoteVersion);
publishToAllRelays(merged);
updateLocalState(merged);
break;
}
}When to show:
- Follow list conflicts (different follows on different relays)
- Profile metadata conflicts (different bio/avatar)
- Kind:10002 relay list conflicts
Pattern 4: Background Sync with Queue
Problem: Network interruptions cause events to fail publishing.
Solution: Queue events for retry when connectivity returns.
Implementation:
class SyncQueue {
constructor() {
this.queue = [];
this.isOnline = navigator.onLine;
window.addEventListener('online', () => this.processQueue());
window.addEventListener('offline', () => this.isOnline = false);
}
async publishEvent(event, relays) {
if (!this.isOnline) {
this.queue.push({ event, relays, attempts: 0 });
showToast("Offline. Will sync when connected.");
return;
}
try {
await this.attemptPublish(event, relays);
} catch (error) {
this.queue.push({ event, relays, attempts: 0 });
this.scheduleRetry();
}
}
async processQueue() {
this.isOnline = true;
while (this.queue.length > 0 && this.isOnline) {
const item = this.queue.shift();
try {
await this.attemptPublish(item.event, item.relays);
} catch (error) {
item.attempts++;
if (item.attempts < 3) {
this.queue.push(item); // Retry
} else {
showError(`Failed to sync ${item.event.kind} after 3 attempts`);
}
}
}
}
async attemptPublish(event, relays) {
const results = await Promise.allSettled(
relays.map(r => r.publish(event))
);
const successful = results.filter(r => r.status === 'fulfilled');
if (successful.length < relays.length * 0.6) {
throw new Error("Insufficient successful publishes");
}
}
scheduleRetry() {
setTimeout(() => this.processQueue(), 5000); // Retry in 5s
}
}
// Usage
const syncQueue = new SyncQueue();
function publishPost(content) {
const event = createKind1Event(content);
syncQueue.publishEvent(event, userRelays);
}User experience:
- Offline: “Offline. Will sync when connected.”
- Connection returns: Auto-sync queued events
- Partial failure: Automatic retry with exponential backoff
- Final failure: User notification with manual retry option
Pattern 5: Relay Health Monitoring
Problem: Users don’t know which relays are working vs. failing.
Solution: Monitor and display relay health.
Implementation:
class RelayHealthMonitor {
constructor(relays) {
this.relays = relays.map(url => ({
url,
status: 'unknown',
lastSuccess: null,
lastFailure: null,
consecutiveFailures: 0,
avgLatency: null
}));
this.startMonitoring();
}
async checkRelayHealth(relay) {
const startTime = Date.now();
try {
await relay.connect();
const latency = Date.now() - startTime;
relay.status = latency < 1000 ? 'healthy' : 'slow';
relay.lastSuccess = Date.now();
relay.consecutiveFailures = 0;
relay.avgLatency = latency;
} catch (error) {
relay.status = 'unreachable';
relay.lastFailure = Date.now();
relay.consecutiveFailures++;
if (relay.consecutiveFailures >= 3) {
this.suggestRelayReplacement(relay);
}
}
}
suggestRelayReplacement(failedRelay) {
showNotification(
"Relay Offline",
`${failedRelay.url} has been unreachable. Would you like to replace it?`,
{
action: 'Replace',
onAction: () => this.showRelayRecommendations()
}
);
}
getHealthSummary() {
return {
healthy: this.relays.filter(r => r.status === 'healthy').length,
slow: this.relays.filter(r => r.status === 'slow').length,
unreachable: this.relays.filter(r => r.status === 'unreachable').length,
total: this.relays.length
};
}
startMonitoring() {
// Check health every 60 seconds
setInterval(() => {
this.relays.forEach(r => this.checkRelayHealth(r));
}, 60000);
}
}
// UI Component
function RelayHealthIndicator({ health }) {
const { healthy, slow, unreachable, total } = health;
return (
<div className="relay-health">
<div className="health-summary">
{healthy}/{total} relays healthy
</div>
{unreachable > 0 && (
<div className="health-warning">
⚠ {unreachable} relay{unreachable > 1 ? 's' : ''} offline
</div>
)}
<button onClick={showDetailedHealth}>View Details</button>
</div>
);
}Anti-Patterns: What Not To Do
Anti-Pattern 1: Silent Data Loss
What it looks like:
User follows 50 people
→ Switches to new client
→ New client shows 0 follows
→ No error, no warning, no explanation
→ User discovers days laterWhy it fails:
- Destroys user trust immediately
- Users assume THEY did something wrong
- No path to recovery
- Makes switching clients terrifying
What to do instead:
- Validate data on every client launch
- “We found 50 follows on relays but 0 locally. Sync now?”
- Show sync progress and completion
- Confirm successful sync: “All 50 follows synced ✓”
Anti-Pattern 2: No Conflict Visibility
What it looks like:
User's profile has:
- Relay A: Avatar = photo1.jpg, Bio = "Developer"
- Relay B: Avatar = photo2.jpg, Bio = "Designer"
- Relay C: Avatar = photo1.jpg, Bio = "Developer"
Client silently picks one, user never knows conflict existsWhy it fails:
- User makes changes that randomly get overwritten
- No understanding of why data keeps reverting
- Can’t fix what they can’t see
- Feels like the app is “broken” or “buggy”
What to do instead:
- Detect conflicts during sync
- Show conflict resolution UI: “We found 2 versions of your profile. Which should we keep?”
- Log conflicts for debugging
- Prevent NEW conflicts by locking edits during sync
Anti-Pattern 3: Unvalidated Optimism
What it looks like:
function followUser(pubkey) {
currentFollows.push(pubkey);
publishKind3(currentFollows); // Fire and forget
updateUI(); // Assume it worked
}Why it fails:
- No validation that publish succeeded
- User sees “Following” but event never reached relays
- False sense of completion
- Data loss discovered later
What to do instead:
- Wait for relay confirmations
- Validate minimum threshold reached (e.g., 3/5 relays)
- Rollback optimistic update on failure
- Show retry option with explanation
Anti-Pattern 4: Technical Sync Details
What it looks like:
Error: Failed to publish kind:3 event to wss://relay.damus.io
REQ subscription timed out after 5000ms
EVENT rejected: duplicate pubkey in tagsWhy it fails:
- Users don’t understand protocol terminology
- Technical jargon creates anxiety
- No actionable next step
- Makes app feel developer-focused, not user-focused
What to do instead:
Can't update your follows right now.
Some servers aren't responding.
We'll keep trying in the background.
[Retry Now] [Cancel]Anti-Pattern 5: No Offline Support
What it looks like:
User on airplane
→ Opens app
→ "No connection" error
→ Can't read old posts
→ Can't queue new posts
→ App is completely unusable offlineWhy it fails:
- Users expect offline functionality (it’s 2025)
- Mainstream apps work offline (Twitter, Instagram, etc.)
- Lost opportunity for engagement
- Users switch to apps that work offline
What to do instead:
- Cache content for offline reading
- Allow composing posts offline
- Queue events for publish when online
- “You’re offline. 3 posts will send when you reconnect.”
Anti-Pattern 6: Stale Cache Overwrites
What it looks like:
1. User has 100 follows on relays
2. New client starts, caches "0 follows" initially
3. Before full sync, user follows 1 person
4. Client publishes kind:3 with [that 1 person]
5. Newer event (1 follow) replaces older event (100 follows)
6. All 100 follows lost foreverWhy it fails:
- Catastrophic data loss
- No way to recover (event is overwritten on relays)
- User loses ALL follows with one action
- Documented repeatedly in [Data:14] [Data:15]
What to do instead:
function followUser(pubkey) {
// BLOCK action until sync completes
if (!kind3FullySynced) {
showWarning("Still loading your follows. Please wait...");
return;
}
// Safe to proceed now
const newFollows = [...currentFollows, pubkey];
publishKind3(newFollows);
}Anti-Pattern 7: Hidden Relay Selection
What it looks like:
Client picks 3 "default" relays internally
User has no idea which relays their data is on
One relay goes offline
User's posts disappear with no explanationWhy it fails:
- User has no control or visibility
- Can’t troubleshoot when things break
- Violates Nostr’s principle of user control
- Creates support burden (“why are my posts missing?”)
What to do instead:
- Show which relays are being used
- Allow ADVANCED users to customize (but smart defaults)
- Indicate relay health status
- Explain impact: “Your posts are saved on 5 servers. 4 are online.”
Validation Checklist
Data Integrity Metrics
Track data loss incidents:
Follow list preservation rate: % of users who maintain their follows across client switches
- Target: >99%
- Track: Follows before switch vs. after switch
Profile metadata consistency: % of users with identical profiles across all relays
- Target: >95%
- Measure: Sample user profiles from random relays, compare
Post availability: % of posts findable across majority of relays
- Target: >95% of posts on ≥3 relays
- Method: Random sampling of recent posts
Event publish success rate: % of events successfully published to majority of relays
- Target: >98%
- Track per event type: kind:0, kind:1, kind:3
Sync Performance Metrics
Measure sync speed:
Time to initial sync: How long from app open to “fully synced”?
- Target: <5 seconds for kind:0, kind:3
- Target: <10 seconds for recent kind:1 feed
Sync conflict rate: How often do conflicts occur?
- Track: Conflicts per 1000 events
- Baseline: Measure current rate
- Goal: Reduce by >50%
Offline queue success: % of queued events that successfully sync
- Target: >95%
- Track: Events queued vs. events successfully published
User Experience Metrics
Trust and confidence:
Data loss complaints: Support tickets about lost follows/posts
- Track weekly
- Goal: <5 reports per 1000 active users
Client switching rate: % of users who successfully switch clients without data loss
- Measure: Users who opened different client within 7 days
- Target: >95% retain all follows
Perceived reliability: User survey
- Question: “Do you trust that your data is safe and consistent?”
- Target: >80% agree
Sync State Visibility
User awareness:
Sync state visibility: % of users who notice sync indicators
- Method: User testing
- Target: >70% can explain what sync status means
Conflict resolution: % of users who successfully resolve conflicts
- Track: Conflicts resolved vs. conflicts that led to data loss
- Target: >90% successfully resolved
Technical Validation
Relay coordination:
Multi-relay publish coverage: % of events published to all configured relays
- Target: >90%
- Alert if drops below 80%
Relay health detection: How quickly are failing relays detected?
- Target: <60 seconds
- Action: Auto-suggest replacement after 3 consecutive failures
Event deduplication: % of duplicate events prevented
- Measure: Events with identical content/timestamp
- Target: 0 duplicates published
A/B Testing Opportunities
Test 1: Sync State Visibility
- A: No sync indicators (current state in many clients)
- B: Persistent sync badge showing real-time status
- Measure: User confidence survey, data loss reports
Test 2: Conflict Resolution Strategy
- A: Auto-resolve using Last Write Wins
- B: Show conflict UI, let user choose
- Measure: Data satisfaction, “lost data” complaints
Test 3: Follow Action Blocking
- A: Allow follow during sync (risk data loss)
- B: Block follow until sync complete with explanation
- Measure: Follow list preservation rate, user frustration
Test 4: Offline Queue Visibility
- A: Silent queuing (no indication)
- B: “3 posts queued. Will send when online.”
- Measure: User understanding, perceived reliability
Citations & Sources
Note: All sources from 2024-2025 to ensure currency.
Universal UX Research
[Research:66] Data Security & Trust: 85% delete apps over privacy; no sector >50% trust (Sidekick Interactive, 2025)
[Research:67] Mobile Privacy: 81% concerned about data handling; 80% see loyalty boost from privacy focus (NowSecure, Feb 2024)
[Research:68] dApp Trust: “UX = trust” in scam-heavy industry; blockchain can’t guarantee DApp quality (The Alien Design, 2024)
[Research:69] Offline Sync Conflicts: Users confused by multi-user offline edits; conflict handling crucial (Mark AI Code, 2025)
[Research:70] Real-Time Sync: 72% expect instant financial data reflection; WebSocket/Firebase essential (Pixel Free Studio, 2024)
[Research:71] Cross-Platform UX: Data sync + clear cross-device interactions = continuity; biggest IoT usability challenge (Medium, October 2025)
[Research:72] Connected Consumer 2025: $896/year on devices; 45% expect content display consistency (Deloitte, 2025)
[Research:73] Consistency & Standards (NN/g): Adherence increases learnability, reduces cognitive load (January 2024)
[Research:74] 10 Usability Heuristics (NN/g): Maintain internal + external consistency for learnability (January 2024)
[Research:75] Material 3 Expressive: 46 studies, 18K participants; common language for cohesive cross-platform (Supercharge Design, 2025)
[Research:76] UX Trends 2025: 59% expect chatbot reply <5s; high expectations for immediate responses (UserGuiding, 2025)
[Research:77] Cross-Device UX 2025: Seamless experience now cornerstone of digital success; frictionless demand (123 Internet, 2025)
[Research:78] PWA 2.0 Offline-First: Offline/Online/Sync state indicators essential; users don’t want “You’re offline” message (Dazzle Birds, 2024)
[Research:79] PWA 2025 Strategies: Pre-cache usage patterns; offline create/edit with auto-sync (Lollypop Studio, October 2025)
[Research:80] Offline-First Development: Smooth experience where users don’t notice they’re offline; sync no longer optional (HashStudioz, 2025)
[Research:81] Offline Sync Conflicts: LWW, CRDTs, manual resolution for seamless conflict handling (Daily.dev, 2024)
[Research:82] Version Vector Pattern: Track updates across nodes for conflict resolution in distributed systems (Shanoj, November 2024)
[Research:83] Distributed Consistency (Endgrate): Use timestamps or manual reconciliation; communicate delay expectations (2024)
[Research:84] State Management (GeeksforGeeks): Enable concurrent access, optimize resources, scale; consistency across replicas challenging (2024)
Nostr-Specific Data
- [Data:12] Relay Data Loss: Damus relay downtime potentially wiped users’ content; reduced to one less relay (Sondre Bjellås, April 2025)
- [Data:14] Catastrophic Follow Loss: User lost 130 follows to 1 when switching to Iris; developer lost 75% follows (Stacker News, 2024)
- [Data:15] Kind 3 Race Conditions: “Very common experience” losing follows; sync failures before follow action delete entire list (GitHub PR #349, 2023-2024)
- [Data:17] Relay Network Decline: 639 relays online (two-thirds reduction), 95% struggle with costs, 20% face downtime (April 2024)
- [Protocol:2] NIP-02: Contact List and Petnames (kind 3) - Replaceable events; must contain all pubkeys as event supersedes previous
- [Protocol:5] NIP-65: Relay List Metadata (kind 10002) - Outbox model for user relay preferences and content discovery
See References & Bibliography for full citation details.
Related Patterns
Next Steps
- Implement sync state visibility: Add persistent sync status indicators for all replaceable events (follows, profile, settings) to give users confidence
- Build conflict detection and resolution: Implement read-before-write patterns for kind:3 events with conflict UI when race conditions detected
- Create offline queue with visual feedback: Show “3 posts queued, will sync when online” messages to maintain user trust during network issues
- Track and measure data loss incidents: Monitor follow list preservation rate (target: 99%+), profile sync success, and cross-client consistency metrics