MindBridge now stores sleep tracking and AI companion data locally on the user's device using IndexedDB. This approach:
- ✅ Privacy First: No sensitive data sent to servers
- ✅ High Performance: Instant access to data
- ✅ Offline Support: Works without internet
- ✅ Large Storage: Can store MBs of data (unlike cookies)
- ✅ Structured Data: Easy to query and manage
| Storage Method | Capacity | Performance | Best For |
|---|---|---|---|
| Cookies | ~4KB | Medium | Small tokens/flags |
| LocalStorage | ~5-10MB | Good | Simple key-value data |
| IndexedDB ✅ | ~50MB+ | Excellent | Complex structured data |
IndexedDB is the best choice because:
- Can store large amounts of data (conversations, sleep logs)
- Supports complex queries and indexing
- Asynchronous (doesn't block UI)
- Can store objects directly (no JSON parsing needed)
- Browser-standard and widely supported
MindBridgeDB (IndexedDB)
├── ai_conversations
│ ├── id (primary key)
│ ├── messages[]
│ ├── startedAt
│ ├── lastUpdated
│ └── mood
├── sleep_tracking
│ ├── id (primary key)
│ ├── date (indexed)
│ ├── bedtime
│ ├── wakeTime
│ ├── duration
│ ├── quality (1-5)
│ └── notes
├── mood_history
│ ├── id (primary key)
│ ├── mood
│ ├── timestamp (indexed)
│ └── activities[]
└── user_preferences
├── theme
├── language
└── notifications
import { useAIConversation } from '@/lib/use-local-storage'
import { v4 as uuidv4 } from 'uuid'
function AICompanionChat() {
const conversationId = 'current-session' // or generate with uuidv4()
const { conversation, addMessage, loading } = useAIConversation(conversationId)
const handleSendMessage = async (content: string) => {
// Save user message locally
await addMessage({
id: uuidv4(),
role: 'user',
content,
timestamp: Date.now(),
mood: currentMood,
})
// Get AI response (from on-device model or API)
const aiResponse = await getAIResponse(content)
// Save AI response locally
await addMessage({
id: uuidv4(),
role: 'assistant',
content: aiResponse,
timestamp: Date.now(),
})
}
return (
<div>
{conversation?.messages.map(msg => (
<div key={msg.id}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
</div>
)
}import { useSleepTracking } from '@/lib/use-local-storage'
import { v4 as uuidv4 } from 'uuid'
function SleepTracker() {
const { sleepEntries, saveSleepEntry, getStats } = useSleepTracking(30)
const [stats, setStats] = useState(null)
const handleSaveSleep = async () => {
await saveSleepEntry({
id: uuidv4(),
date: '2025-10-14',
bedtime: '23:00',
wakeTime: '07:00',
duration: 8,
quality: 4,
notes: 'Slept well, no interruptions',
mood: 'calm',
createdAt: Date.now(),
})
}
const loadStats = async () => {
const stats = await getStats(30) // Last 30 days
setStats(stats)
}
return (
<div>
<h2>Sleep Entries</h2>
{sleepEntries.map(entry => (
<div key={entry.id}>
{entry.date}: {entry.duration}h (Quality: {entry.quality}/5)
</div>
))}
{stats && (
<div>
<p>Average Duration: {stats.averageDuration.toFixed(1)}h</p>
<p>Average Quality: {stats.averageQuality.toFixed(1)}/5</p>
</div>
)}
</div>
)
}import { useMoodHistory } from '@/lib/use-local-storage'
import { v4 as uuidv4 } from 'uuid'
function MoodTracker() {
const { moodEntries, saveMoodEntry } = useMoodHistory(7)
const handleSaveMood = async (mood: string) => {
await saveMoodEntry({
id: uuidv4(),
mood,
timestamp: Date.now(),
notes: 'Feeling better today',
activities: ['meditation', 'exercise'],
})
}
return (
<div>
{moodEntries.map(entry => (
<div key={entry.id}>
{new Date(entry.timestamp).toLocaleDateString()}: {entry.mood}
</div>
))}
</div>
)
}import { exportAllData } from '@/lib/local-storage'
const handleExportData = async () => {
const jsonData = await exportAllData()
const blob = new Blob([jsonData], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `mindbridge-data-${new Date().toISOString()}.json`
a.click()
}import { clearAllLocalData } from '@/lib/local-storage'
const handleClearData = async () => {
if (confirm('Delete all local data? This cannot be undone.')) {
await clearAllLocalData()
alert('All data deleted successfully')
}
}import { getStorageInfo } from '@/lib/local-storage'
const handleCheckStorage = async () => {
const info = await getStorageInfo()
console.log('Storage Info:', info)
// {
// conversationsCount: 15,
// sleepEntriesCount: 30,
// moodEntriesCount: 45,
// estimatedSize: "2.34 MB"
// }
}// app/layout.tsx
import { initDB } from '@/lib/local-storage'
export default function RootLayout({ children }) {
useEffect(() => {
// Initialize IndexedDB when app loads
initDB().catch(err => {
console.error('Failed to initialize local storage:', err)
})
}, [])
return <html>{children}</html>
}Replace Convex storage calls with local storage:
// Before (using Convex)
const saveMessage = useMutation(api.messages.send)
await saveMessage({ content, userId })
// After (using IndexedDB)
import { aiStorage } from '@/lib/local-storage'
await aiStorage.addMessage(conversationId, { content, ... })// Before (using Convex)
const saveSleep = useMutation(api.sleep.create)
await saveSleep({ date, duration, quality })
// After (using IndexedDB)
import { sleepStorage } from '@/lib/local-storage'
await sleepStorage.saveSleepEntry({ date, duration, quality, ... })try {
await aiStorage.addMessage(conversationId, message)
} catch (error) {
console.error('Failed to save message locally:', error)
// Show user-friendly error
toast.error('Failed to save message. Please try again.')
}const { conversations, loading, error } = useAIConversations()
if (loading) return <Spinner />
if (error) return <ErrorMessage error={error} />
return <ConversationList conversations={conversations} />// Clean up old conversations (older than 90 days)
useEffect(() => {
const cleanup = async () => {
const conversations = await aiStorage.getAllConversations()
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000)
for (const conv of conversations) {
if (conv.lastUpdated < ninetyDaysAgo) {
await aiStorage.deleteConversation(conv.id)
}
}
}
cleanup()
}, [])// Add to settings page
const PrivacySettings = () => {
return (
<div>
<h2>Data Privacy</h2>
<p>All data is stored locally on your device</p>
<Button onClick={() => exportAllData()}>
Download My Data
</Button>
<Button onClick={() => clearAllLocalData()} variant="destructive">
Delete All Local Data
</Button>
<StorageInfo />
</div>
)
}// Instead of multiple individual saves
for (const message of messages) {
await aiStorage.addMessage(conversationId, message) // Slow
}
// Batch save
const conversation = await aiStorage.getConversation(conversationId)
conversation.messages.push(...messages)
await aiStorage.saveConversation(conversation) // Fast// Only load recent conversations initially
const { conversations } = useAIConversations()
const recentConvs = conversations.slice(0, 10)
// Load more on demand
const loadMore = async () => {
const older = await aiStorage.getAllConversations()
// ... display older conversations
}Indexes are already created for common queries:
dateindex on sleep_trackingtimestampindex on mood_historylastUpdatedindex on ai_conversations
IndexedDB is supported in all modern browsers:
- ✅ Chrome/Edge 24+
- ✅ Firefox 16+
- ✅ Safari 10+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
- Data is NOT encrypted by default - IndexedDB stores data in plain text
- Same-origin policy - Data is isolated per domain
- User can clear - Browser settings can delete IndexedDB data
- No server sync - Data stays on device (feature, not bug!)
For sensitive data, you can add encryption:
import CryptoJS from 'crypto-js'
const encrypt = (data: string, key: string) => {
return CryptoJS.AES.encrypt(data, key).toString()
}
const decrypt = (encryptedData: string, key: string) => {
const bytes = CryptoJS.AES.decrypt(encryptedData, key)
return bytes.toString(CryptoJS.enc.Utf8)
}
// Use user's password or device-specific key
const userKey = getUserEncryptionKey()
// Encrypt before storing
const encryptedContent = encrypt(message.content, userKey)
await aiStorage.addMessage(conversationId, {
...message,
content: encryptedContent
})
// Decrypt when reading
const decryptedContent = decrypt(message.content, userKey)// Test sleep storage
describe('Sleep Tracking', () => {
it('should save and retrieve sleep entry', async () => {
const entry = {
id: 'test-1',
date: '2025-10-14',
duration: 8,
quality: 4,
}
await sleepStorage.saveSleepEntry(entry)
const retrieved = await sleepStorage.getSleepByDate('2025-10-14')
expect(retrieved).toEqual(entry)
})
})✅ Implemented: Complete local storage system using IndexedDB
✅ Features: AI conversations, sleep tracking, mood history, preferences
✅ React Hooks: Easy-to-use hooks for all storage operations
✅ Privacy: All data stays on device
✅ Performance: Fast, efficient, offline-capable
✅ Scalable: Can store MBs of data
Next Steps:
- Update AI Companion component to use
useAIConversationhook - Update Sleep Tracker to use
useSleepTrackinghook - Add export/clear data buttons in settings
- Test thoroughly in different browsers
- Add encryption for extra security (optional)