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:
- User logs in → Auth state set in memory
autoFlashState()triggers asyncdispatch()to persist- User navigates/refreshes → Page reloads
- Async write hasn't completed yet → Auth state lost
- 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:
- State updates in memory immediately ✅
- But persistence happens asynchronously ⏳
- If page reloads before async write completes → data lost ❌
Why Flush Doesn't Always Help
Even with flushPendingPersistence() called before navigation:
- Navigation triggers flush ✅
- Flush awaits pending writes ✅
- BUT something causes full page reload (HMR, browser refresh, etc.)
- 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
- localStorage.setItem() is synchronous → Completes immediately
- Restore runs in constructor → Before auth guard checks
- Auth guard sees authenticated state → No redirect loop
- Flash Persistence still primary → Respects storage system architecture
- localStorage is just backup → For critical data reliability
Industry Standard Pattern
This is the standard pattern for auth persistence:
- ✅ Primary: Sophisticated async storage (IndexedDB, Cloud sync)
- ✅ Backup: Fast sync storage (localStorage) for critical data
- ✅ Examples: Firebase, Auth0, AWS Amplify all do this
Files Modified
Core Fix:
libs/auth-ui/src/lib/services/auth.service.ts- Added
restoreFromBackup()- synchronous restore - Added
syncWithFlashPersistence()- async sync - Modified
setAuthState()- dual write (Flash + localStorage) - Modified
clearAuth()- clear both storages
- Added
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:
- ✅ Log in successfully
- ✅ Navigate to protected route
- ✅ Refresh page (F5 or Cmd+R)
- ✅ Still logged in (no redirect to login)
- ✅ Close browser completely
- ✅ Reopen browser
- ✅ Still logged in
- ✅ Auth persists across full page reloads
- ✅ Auth persists across navigation
Prevention for Future
For ANY critical user data (auth, session, cart, etc.):
- Use Flash Persistence as primary ✅
- Add synchronous localStorage backup ✅
- Restore synchronously in constructor ✅
- Clear both on logout ✅
Rule of Thumb:
- Critical data (user loses session if lost): Hybrid approach
- Non-critical data (user inconvenienced if lost): Flash Persistence only
📋 Related Issues
Potential Similar Issues
Watch for similar race conditions in:
- Shopping cart persistence
- Form draft auto-save
- User preferences (theme, language)
- Active workspace/project state
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
- Flash Persistence:
libs/storage-system/src/flash/ - Auto Flash State:
libs/storage-system/src/flash/helpers/auto-flash-state.ts - Auth Service:
libs/auth-ui/src/lib/services/auth.service.ts - Issue Thread: October 28, 2025 - Authentication persistence bug
✅ 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)
- Added
AutoFlashSignal<T>.hydrated— per-state hydration signal (auto-flash-state.ts) - Hydration gate — backend loading gated on
state.hydrated()to prevent race conditions - Write backend data INTO autoFlashState — single signal, no
_backendSessionsneeded - Atomic merge — no flash because it's one
state.update(), not remove-then-add - Fixed
debounceMsdead code — was defined in options but never consumed - Consolidated duplicate backend loading — removed duplicate in
VittoChatEngineService - Wired up
provideBackendSyncTransport()— enablesBackendSyncEnginefor 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)