Known Issues

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Storage System - Known Issues & Solutions

Date: October 28, 2025
Last Updated: October 28, 2025
Status: 🔧 ACTIVE DOCUMENTATION


🔴 CRITICAL: Flash Persistence Async Write Race Condition

Issue ID: FLASH-PERSIST-001
Severity: 🔴 CRITICAL
Affected: Authentication, Critical User Data
Status: ✅ RESOLVED

Problem Description

Flash Persistence uses async dispatch (fire-and-forget) when persisting state changes. This creates a race condition where:

  1. User logs in → Auth state set in memory
  2. autoFlashState() triggers async dispatch() to persist
  3. User navigates/refreshes → Page reloads
  4. Async write hasn't completed yet → Auth state lost
  5. User redirected to login (appears logged out)

Root Cause Analysis

// In auto-flash-state.ts line 126
flashState.dispatch(value, { type: 'SET_VALUE', source: 'autoFlashState' }).catch((e: any) =>
  console.warn(`Failed to persist ${fullKey}:`, e)
);

The dispatch is NOT awaited! It's fire-and-forget, meaning:

Why Flush Doesn't Always Help

Even with flushPendingPersistence() called before navigation:

  1. Navigation triggers flush ✅
  2. Flush awaits pending writes ✅
  3. BUT something causes full page reload (HMR, browser refresh, etc.)
  4. Full reload reinitializes everything before async writes complete ❌

Solution: Hybrid Approach

Use BOTH Flash Persistence + Synchronous localStorage Backup

// In AuthService.setAuthState()
private setAuthState(response: AuthResponse): void {
  const newState: AuthState = {
    user: response.user,
    accessToken: response.accessToken,
    refreshToken: response.refreshToken || null,
    isAuthenticated: true,
    isLoading: false,
    error: null
  };

  // ✅ PRIMARY: Set in Flash Persistence (async)
  this._authState.set(newState);

  // ✅ BACKUP: Write synchronously to localStorage
  try {
    localStorage.setItem('auth_backup', JSON.stringify(newState));
    console.log('✅ Auth state backed up to localStorage synchronously');
  } catch (e) {
    console.warn('⚠️ Failed to backup auth to localStorage:', e);
  }
}

// In constructor - restore SYNCHRONOUSLY before auth guard runs
constructor() {
  this.apiUrl = backendUrl || '/api';
  
  // ✅ CRITICAL: Restore SYNCHRONOUSLY (not in setTimeout!)
  this.restoreFromBackup();  // Runs immediately
  
  // Then sync with Flash Persistence later
  setTimeout(() => this.syncWithFlashPersistence(), 100);
}

private restoreFromBackup(): void {
  try {
    const backup = localStorage.getItem('auth_backup');
    if (backup) {
      const backupState = JSON.parse(backup) as AuthState;
      
      // Validate token
      if (backupState.accessToken && this.isTokenValid(backupState.accessToken)) {
        console.log('✅ [SYNC] Restored from localStorage backup');
        this._authState.set(backupState);  // Loads BEFORE auth guard runs!
      }
    }
  } catch (e) {
    console.warn('⚠️ Failed to restore from backup:', e);
  }
}

Why This Works

  1. localStorage.setItem() is synchronous → Completes immediately
  2. Restore runs in constructor → Before auth guard checks
  3. Auth guard sees authenticated state → No redirect loop
  4. Flash Persistence still primary → Respects storage system architecture
  5. localStorage is just backup → For critical data reliability

Industry Standard Pattern

This is the standard pattern for auth persistence:

Files Modified

Core Fix:

Flash Persistence Options Used:

autoFlashState<AuthState>('auth/state', defaultValue, {
  priority: 'critical',  // High priority for auth
  debounceMs: 0          // No debounce - persist immediately
})

Configuration

For Critical Auth Data:

// ✅ GOOD: Use hybrid approach
const authState = autoFlashState('auth/state', defaultValue, {
  priority: 'critical',
  debounceMs: 0
});

// ALSO add localStorage backup
localStorage.setItem('auth_backup', JSON.stringify(state));

For Non-Critical Data:

// ✅ FINE: Flash Persistence alone is sufficient
const preferences = autoFlashState('user-prefs', defaultValue);

Testing Checklist

To verify the fix works:

Prevention for Future

For ANY critical user data (auth, session, cart, etc.):

  1. Use Flash Persistence as primary ✅
  2. Add synchronous localStorage backup ✅
  3. Restore synchronously in constructor ✅
  4. Clear both on logout ✅

Rule of Thumb:


📋 Related Issues

Potential Similar Issues

Watch for similar race conditions in:

If ANY of these show "lost after refresh", apply the same hybrid pattern.


🔧 Long-term Solution (Future Enhancement)

Enhance Flash Persistence to support synchronous writes for critical data:

// Future API proposal
autoFlashState('auth/state', defaultValue, {
  priority: 'critical',
  syncWrite: true,  // ← Force synchronous write to localStorage
  asyncWrite: true  // ← Also write to IndexedDB async
});

This would make the hybrid pattern automatic for critical data.


📚 References


✅ Resolution Status

Status: ✅ RESOLVED (October 28, 2025) Solution: Hybrid Flash Persistence + localStorage backup Verified: Auth persists across refreshes and navigation Pattern: Documented and can be reused for other critical data


🟢 RESOLVED: autoFlashState ↔ MongoDB Sync & Session Flash

Issue ID: CACHE-SYNC-003 Severity: 🔴 CRITICAL Affected: Chat sessions, any feature using autoFlashState + backend sync Status: ✅ RESOLVED (Session 172)

Problem Description

On page reload, chat sessions "flashed" — stale sessions from IndexedDB appeared for ~500ms, then disappeared when backend data arrived. Root cause: autoFlashState (IndexedDB) and MongoDB were permanently out of sync. The architecture used THREE separate stores (localStorage, autoFlashState, _backendSessions signal), and loadBackendSessions() never wrote backend data back into autoFlashState. IndexedDB kept stale data forever.

Solution: autoFlashState as Cache Layer (Session 172)

  1. Added AutoFlashSignal<T>.hydrated — per-state hydration signal (auto-flash-state.ts)
  2. Hydration gate — backend loading gated on state.hydrated() to prevent race conditions
  3. Write backend data INTO autoFlashState — single signal, no _backendSessions needed
  4. Atomic merge — no flash because it's one state.update(), not remove-then-add
  5. Fixed debounceMs dead code — was defined in options but never consumed
  6. Consolidated duplicate backend loading — removed duplicate in VittoChatEngineService
  7. Wired up provideBackendSyncTransport() — enables BackendSyncEngine for future features

Pattern for Future Features

private readonly state = autoFlashState<MyState>('key', defaultState, config);

constructor() {
  effect(() => {
    if (!this.state.hydrated()) return; // Gate on hydration
    // ... load from backend, write INTO state ...
  });
}

Status: ✅ RESOLVED (Session 172, March 3, 2026)


Last Verified: March 3, 2026 Next Review: When adding new critical user data persistence


🟡 RESOLVED: State Versioning Rollback Data Loss

Issue ID: STATE-VERSION-002 Severity: 🟡 MEDIUM Affected: All autoFlashState data on deployment rollbacks Status: ✅ RESOLVED (Session 166.3)

Problem Description

When a deployment is rolled back (storedVersion > currentVersion), the state versioning system returned defaultValue — nuking all user data silently. A user who had customized preferences, saved drafts, etc. would lose everything on a rollback.

Root Cause

// BEFORE (state-versioning.ts):
if (storedVersion > currentVersion) {
  return defaultValue; // ← ALL USER DATA LOST
}

Solution: Preserve Known Fields

// AFTER (Session 166.3):
if (storedVersion > currentVersion) {
  // Strip unknown fields but preserve everything the current version knows about
  const knownFields = Object.keys(defaultValue);
  const preserved = {};
  for (const field of knownFields) {
    preserved[field] = storedData[field] ?? defaultValue[field];
  }
  return preserved as T;
}

File: libs/libs-foundational/storage-system/src/flash/utils/state-versioning.ts (lines 86-112)

Also Added: Cross-Tab Migration Coordination

When one tab migrates a state key, it broadcasts a schema-migrated event via BroadcastChannel so other tabs invalidate their cache and re-read from storage. Prevents stale-version reads across tabs.

Status: ✅ RESOLVED (Session 166.3, March 3, 2026)