Storage Mode Refactor - Exclusive Storage (No Hybrid)
Summary
Refactored CAIPE UI from hybrid storage (MongoDB + localStorage simultaneously) to exclusive storage mode (MongoDB OR localStorage, never both).
Problem Statement
Before:
- App always used localStorage (via Zustand persist)
- Also synced to MongoDB if available
- Confusing dual-write behavior
- Users didn't know where their data lived
- Old conversations could be in localStorage, new ones in MongoDB
After:
- Clean separation: MongoDB XOR localStorage
- Storage mode determined by
MONGODB_URIenv variable - No confusion about data location
- Clear UI indicators
Changes Made
1. New Storage Configuration System
File: ui/src/lib/storage-config.ts
// Replaces old storage-mode.ts with simpler, exclusive logic
export const IS_MONGODB_CONFIGURED = !!(MONGODB_URI && MONGODB_DATABASE);
export function getStorageMode(): 'mongodb' | 'localStorage' {
return IS_MONGODB_CONFIGURED ? 'mongodb' : 'localStorage';
}
export function shouldUseLocalStorage(): boolean {
return !IS_MONGODB_CONFIGURED;
}
Key difference from old storage-mode.ts:
- Old: Async API check for MongoDB availability (runtime)
- New: Build-time env variable check (static)
- Simpler, faster, no race conditions
2. Conditional Zustand Persistence
File: ui/src/store/chat-store.ts
Before (hybrid):
export const useChatStore = create<ChatState>()(
persist(storeImplementation, { ... }) // Always persisted to localStorage
);
// Separately sync to MongoDB
if (await isMongoDBAvailable()) {
await apiClient.createConversation({ ... });
}
After (exclusive):
export const useChatStore = shouldUseLocalStorage()
? create<ChatState>()(persist(storeImplementation, { ... })) // localStorage mode
: create<ChatState>()(storeImplementation); // MongoDB mode (no persistence)
Impact:
- localStorage mode: Zustand persist enabled → data saved in browser
- MongoDB mode: Zustand persist disabled → data only in MongoDB
3. Exclusive CRUD Operations
Updated methods:
createConversation()- No longer dual-writesdeleteConversation()- Deletes from active storage onlysyncConversationsFromMongoDB()→loadConversationsFromServer()- Clearer naming
Example (createConversation):
const storageMode = getStorageMode();
if (storageMode === 'mongodb') {
// Create on server
await apiClient.createConversation({ ... });
}
// Update local state (only persisted in localStorage mode)
set({ conversations: [...] });
4. Updated Components
Sidebar.tsx
Before:
import { getCachedStorageMode } from "@/lib/storage-mode";
const [storageMode, setStorageMode] = useState<'mongodb' | 'localStorage' | null>(null);
useEffect(() => {
syncConversationsFromMongoDB();
setTimeout(() => {
setStorageMode(getCachedStorageMode());
}, 500);
}, [activeTab]);
After:
import { getStorageMode, getStorageModeDisplay } from "@/lib/storage-config";
const storageMode = getStorageMode(); // Synchronous, no state needed
useEffect(() => {
loadConversationsFromServer(); // Only loads if MongoDB mode
}, [activeTab]);
Benefits:
- No async storage detection
- No setTimeout hacks
- Immediate, deterministic
5. Enhanced UI Indicators
Sidebar Storage Mode Indicator:
localStorage mode:
⚠️ Local Storage Mode
Browser-only • Not shareable
MongoDB mode:
✅ MongoDB Mode
Persistent • Shareable • Teams
Shows users exactly where their data lives.
6. Environment Configuration
ui/env.example:
# ===========================================
# Storage Configuration - MongoDB vs localStorage (EXCLUSIVE)
# ===========================================
#
# IMPORTANT: The app uses ONLY ONE storage mode (not hybrid)
# - MongoDB mode: Persistent, shareable, team collaboration
# - localStorage mode: Browser-only, not shareable
#
# Set MONGODB_URI to enable MongoDB mode
# Leave unset for localStorage mode
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=caipe
ui/.env.local (updated with clear comments):
# ===========================================
# Storage Mode: MongoDB vs localStorage (EXCLUSIVE)
# ===========================================
#
# ✅ MongoDB mode (current): Persistent, shareable, team collaboration
# - Conversations stored in MongoDB
# - localStorage NOT used
# - Admin features enabled
#
# To switch to localStorage mode: Comment out MONGODB_URI
# - Conversations stored in browser only
# - Not shareable
# - No admin features
#
MONGODB_URI=mongodb://admin:changeme@localhost:27017
MONGODB_DATABASE=caipe
Migration Path
For Users with Existing localStorage Data
If you have conversations in localStorage and want to migrate to MongoDB:
- Before: localStorage mode (no MongoDB configured)
- Export localStorage data (if needed for backup)
- Configure MongoDB in
.env.local - Restart the app
- Migration happens via admin API (separate feature)
For Development
Testing localStorage mode:
cd ui
# Comment out MONGODB_URI in .env.local
npm run dev
Testing MongoDB mode:
cd ui
# Ensure MONGODB_URI is set in .env.local
npm run dev
Files Modified
Core Changes
- ✅
ui/src/lib/storage-config.ts- NEW: Exclusive storage mode logic - ✅
ui/src/store/chat-store.ts- Conditional Zustand persistence - ✅
ui/src/components/layout/Sidebar.tsx- Updated storage indicators - ✅
ui/src/app/(app)/chat/page.tsx- Use newloadConversationsFromServer() - ✅
ui/src/app/(app)/admin/page.tsx- Removed hybrid migration tool
Documentation
- ✅
docs/storage-modes.md- NEW: Comprehensive storage mode docs - ✅
ui/env.example- Updated with exclusive storage comments - ✅
ui/.env.local- Clear mode indicator comments - ✅
STORAGE_MODE_REFACTOR.md- This file
Deprecated (can be removed after testing)
- ⚠️
ui/src/lib/storage-mode.ts- Replaced bystorage-config.ts - ⚠️
ui/src/app/api/admin/migrate-conversations/route.ts- Old migration API
Benefits
For Users
✅ Clear data location - No confusion about where conversations live ✅ Predictable behavior - One storage mode, not two ✅ Visual indicators - Always know what mode you're in ✅ No surprises - Data doesn't mysteriously appear/disappear
For Developers
✅ Simpler logic - No dual-write complexity ✅ Fewer bugs - No sync race conditions ✅ Easier testing - Test one mode at a time ✅ Better performance - No unnecessary localStorage writes in MongoDB mode
For Admins
✅ MongoDB is source of truth - No localStorage confusion ✅ Better analytics - All data in one place ✅ Team features work - Sharing, collaboration enabled
Testing Checklist
localStorage Mode
- Conversations persist in browser
- Conversations NOT sent to server
- Sidebar shows "Local Storage Mode"
- Admin features disabled
- Works without MongoDB configured
MongoDB Mode
- Conversations saved to MongoDB
- localStorage NOT used for persistence
- Sidebar shows "MongoDB Mode"
- Admin features enabled
- Sharing/teams work
Mode Switching
- localStorage → MongoDB: Data migrates cleanly
- MongoDB → localStorage: Graceful fallback
- No data loss
- Clear error messages
Rollout Plan
Phase 1: Internal Testing ✅ (Current)
- Test with dev team
- Verify both modes work
- Check migration paths
Phase 2: Staged Rollout
- Enable for power users first
- Monitor for issues
- Gather feedback
Phase 3: Full Deployment
- Update all environments
- Update documentation
- Train support team
Support
Common Issues
Q: My conversations disappeared! A: Check storage mode. If you switched from MongoDB → localStorage, you need to re-enable MongoDB.
Q: I see old conversations but not new ones
A: You may have switched storage modes. Check .env.local and restart.
Q: Can I use both at once? A: No. Exclusive storage mode prevents confusion.
Future Enhancements
- Automatic migration wizard in UI
- Export/import conversations
- Storage mode selector in settings
- Data sync across devices (MongoDB mode)
Credits
Author: Sri Aradhyula (sraradhy@cisco.com) Date: 2026-01-30 Version: v1.0 (Exclusive Storage Mode) Related: Admin Dashboard, Team Features, Storage Architecture