fix: journal entries not saving to Supabase database#3
Conversation
…rence
- Introduce LLMProvider interface enabling pluggable LLM strategies (cloud/local)
- Implement WebLLMService for in-browser inference using @mlc-ai/web-llm
- Implement OpenAILLMProvider wrapping existing OpenAI client
- Add providerFactory with model selection (1.5B, 3B, 7B+ options)
- Extend settings store with aiProvider ('cloud'|'local') and localModelId
- Create useLLMProvider React hook for provider lifecycle management
- Add comprehensive unit tests for WebLLM and provider factory
- Update supabaseService to handle new AI configuration fields
Supported WebLLM models:
- 1.5B: Qwen2-1.5B-Instruct-q4f32_1-MLC
- 3B: Phi-3.5-mini, Gemma-2-9b-it
- 7B+: Llama-3.2-1B, Llama-3.1-8B, Mistral-7B-Instruct-v0.3
Architecture follows SOLID principles:
- Single Responsibility: Each component handles one concern
- Dependency Inversion: High-level code depends on LLMProvider interface
- Open/Closed: New providers can be added without modifying existing code
All new code is type-safe, well-documented with docstrings, and follows TDD.
- Add AIProviderSettings component for cloud/local provider selection - Implement ModelDownloadProgress component for WebLLM initialization UI - Create useLLMService hook for high-level provider access - Add adapterService for global provider state management - Integrate AIProviderSettings into Settings page - Update useLLMProvider to set global provider state - Use only shadcn/ui components for consistency with design system Features: - Radio button interface for provider selection - Dropdown to choose from 7+ available models (1.5B, 3B, 7B+ sizes) - Real-time download progress display during model initialization - Helpful UI hints and descriptions for each provider type - Automatic provider switching when settings change - Global provider access for non-React code via adapterService UI components used: - Card, CardContent, CardDescription, CardHeader, CardTitle - Select, SelectContent, SelectItem, SelectTrigger, SelectValue - Label (for form labels) - Custom HTML radio buttons for better integration - Tailwind CSS for styling and alerts All new code is type-safe, well-documented, and follows SOLID principles.
- Remove useLLMProvider hook from AIProviderSettings component - This prevents auto-initialization of WebLLM models in settings UI - Models now only initialize when actually needed during inference - Settings page is now a safe configuration interface Root cause: useLLMProvider was attempting to download and load large model files when settings page loaded, causing browser to hang/crash Solution: Lazy-initialize models only when features that need them are actually used (chat, reflections, etc)
- Add 'Download & Initialize Model' button in settings for local models - Users must explicitly download model before using local inference - Show real-time progress during model initialization in settings - Add error state with retry button if download fails - Create aiProviderWrapper to route calls to appropriate LLM provider - Routes to WebLLM when local mode + provider initialized - Falls back to OpenAI when cloud mode or provider not ready - Allows gradual migration of existing aiService code UI Features: - Large download button in settings when local mode selected - Real-time progress display with percentage and status - Error handling with helpful messages - Clear information about download requirements This ensures: 1. Models are only downloaded when explicitly requested 2. Settings page doesn't crash from auto-initialization 3. Existing code can be gradually migrated to use new provider system 4. Clear UX indicating whether local inference is ready
…dicators - Update streamRealtimeReflection in aiService to use provider wrapper - Routes to WebLLM when local mode enabled and model downloaded - Falls back to OpenAI (cloud) if local not configured/ready - Shows warning to user if local selected but model not downloaded - Update useRealtimeReflection hook to track inference state - Returns isInferring and inferenceError state - Checks provider status before starting inference - Sets error if local mode but model not initialized - Add loading indicators to ChatPanel - Shows 'Local Processing' indicator with pulse when using WebLLM - Shows 'Thinking...' indicator with pulse when using Cloud API - Different colors for local (amber) vs cloud (blue) to distinguish - Displays error messages if inference fails - Error handling - Clear warning if user selects local but hasn't downloaded model - Graceful fallback to cloud if local inference fails - User-friendly error messages in chat UI This ensures users see real-time feedback about: 1. Whether inference is happening 2. Which provider is being used (local CPU/GPU or cloud) 3. Any errors that prevent inference from working
- Add detailed logging to WebLLM initialization for debugging - Provide helpful error messages for common download failures - Explains cache/network issues - Suggests clearing browser cache - Notes repository availability - Implement automatic retry logic with exponential backoff - Retries failed downloads up to 3 times - Waits 2s, 4s, 8s between attempts - Better resilience to temporary network hiccups - Enhance error display in settings UI - Shows full error message (preserved formatting) - User-friendly error layout - Add comprehensive troubleshooting section - Model download failing: network/cache solutions - Model too slow: performance optimization tips - Instructions for clearing browser cache - Browser compatibility recommendations This addresses the 'Cache network error' issue by: 1. Providing clear guidance on troubleshooting 2. Automatically retrying failed downloads 3. Explaining what went wrong with actionable steps 4. Helping users diagnose system limitations
UI Components: - Create VoiceInput component with beautiful recording state machine - Record button with animated pulse during recording - Stop button appears during recording - Real-time transcription processing UI - Success/error states with icons and colors - Smooth transitions between states - Integrate VoiceInput into ChatPanel - Voice input section appears when Whisper enabled - Transcribed text automatically added as user message - Seamless integration with existing chat flow Settings Modernization: - Replace manual HTML checkboxes with shadcn/ui Checkbox components - Convert 5 settings at bottom to proper shadcn UI components: - Auto Reflect on Entries - Enable Memories (local & sync) - Show Contextual Nudges - Enable Voice Input (Whisper) - Enable Anonymized Sharing - Add feature descriptions for each toggle - Group all feature toggles in dedicated section with heading - Improve spacing and visual hierarchy Design Details: - Recording state: red pulse indicator with 'Recording...' text - Processing state: blue spinner with 'Transcribing...' text - Success state: green checkmark with 'Transcribed!' text - Error state: red alert icon with error message - All states have smooth 1.5-2s auto-reset after completion This makes Whisper accessible and visible, transforming it from hidden setting into a first-class feature seamlessly integrated into chat UI.
Configuration: - Add Vite proxy to route /api/whisper requests to localhost:8788 - Whisper server runs separately during development - Production builds don't need this (API calls go to deployment server) Scripts: - npm run dev:whisper - Start only Whisper proxy server on port 8788 - npm run dev:full - Start both Whisper server + Vite dev server - npm run dev - Start only Vite (existing, for when Whisper server runs separately) To use voice recording in development: 1. Option A (Recommended): npm run dev:full - Starts both servers automatically - Whisper at http://localhost:8788 - App at http://localhost:5173 2. Option B (Manual): - Terminal 1: npm run dev:whisper - Terminal 2: npm run dev Installation: - Added concurrently as devDependency to run both servers This fixes the 404 error when attempting to use Whisper voice input by ensuring the proxy endpoint is available during development.
Changed from: tsx server/whisperServer.ts Changed to: bun run server/whisperServer.ts bun natively supports running TypeScript files without additional tooling. This fixes the 'tsx: command not found' error when running npm run dev:full
Integration:
- Add useLLMProvider hook to AIProvider component
- Provider automatically initializes based on settings
- Sets global provider for access from aiService
- Handles provider lifecycle (creation, initialization, cleanup)
Provider Initialization Flow:
1. AIProvider renders (at app root)
2. useLLMProvider hook runs
3. Checks aiProvider setting ('cloud' or 'local')
4. If local: Creates WebLLMService with selected modelId
5. If autoInitialize: Starts model download
6. Sets provider as global (accessible from aiService)
7. On settings change: Provider is recreated automatically
This ensures local LLM inference is fully wired up:
- streamRealtimeReflection checks global provider
- getStreamingResponse uses provider if available
- Falls back to OpenAI if provider unavailable or errors
- All inference automatically uses local model when enabled
…LLM as an AI provider. - save point 1/17/26 (not working yet)
…yer with local and Supabase repositories, and end-to-end encryption for data.
- Create IAuthService interface for authentication operations - Create IJournalService interface for journal entry CRUD - Create IConversationService interface for thread/message management - Create IUserService interface for user profiles and settings - Create IFeedbackService interface for feedback and stash operations - Establish foundation for SOLID principles with dependency injection
- Create AuthService class implementing IAuthService interface - Extract authentication logic from supabaseService.ts - Handle user registration, login (email/password + Google OAuth), logout - Include user profile creation and settings initialization - Maintain compatibility with existing error handling patterns - Follow SOLID principles with single responsibility for auth operations
…sage import, complete FeedbackService session handling - Fix ConversationService to use Message interface from journalStore.ts (includes threadId, isRealtimeReflection, reflectedContent, isRead) - Implement proper session ID generation and persistence in FeedbackService using localStorage - Remove unused SupabaseError import and fix linting issues in FeedbackService - All domain services (Journal, Conversation, User, Feedback) now properly implemented with dependency injection
…ervices into SupabaseRepository - Update SupabaseRepository constructor to accept AuthService, JournalService, ConversationService, FeedbackService via dependency injection - Replace direct supabaseService calls with service method calls (journalService for entries, conversationService for threads/messages, feedbackService for stash) - Add proper type mapping between journal Message and conversation Message interfaces - Update RepositoryFactory to instantiate and inject the new services - All repository methods now use the modular services instead of monolithic supabaseService
- Update UI components to use modular services instead of supabaseService imports - Move SupabaseError and UserProfile types to shared types file - Update all service interfaces and implementations to import from shared types - Delete monolithic supabaseService.ts file - Export service instances from RepositoryFactory for UI component access BREAKING CHANGE: supabaseService.ts removed, all functionality now in modular services
- Update login-form, sign-up-form, update-password-form, forgot-password-form to use authService - Add updatePassword and resetPasswordForEmail methods to AuthService - Update Reports.tsx and memoryAutoSave.ts to use authService.getCurrentUser() - All UI components now use modular services instead of direct supabase calls BREAKING CHANGE: UI components no longer use supabase.auth directly, now use AuthService
- Wrap app with AuthProvider for authentication context - Add protected routes for main app content - Add public /login route for authentication - Include AccountSettings component in Settings page for profile management - Users can now login/logout and manage profile via Settings > Account
- Change date field in createJournalEntry and updateJournalEntry to use DATE format (YYYY-MM-DD) - Prevents type mismatch with Supabase DATE column - Fixes journal entries saving to database
❌ Deploy Preview for echo-journal failed. Why did it fail? →
|
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive refactoring of Echo Journal's architecture, introducing modular services, dual storage support (local/cloud), WebLLM for local AI inference, and several new features. The stated goal is to fix journal entry saving issues while modernizing the codebase following SOLID principles.
Changes:
- Modularizes Supabase integration into focused services (Auth, Journal, Conversation, User, Feedback)
- Adds WebLLM support for local AI inference alongside cloud providers
- Implements robust IndexedDB storage via localforage to replace localStorage
- Adds new features: semantic search, topic clustering, insight reports, voice input, E2E encryption
- Updates authentication flow with new login/signup UI components
- Refactors stores to use robust storage and integrate with repository pattern
Reviewed changes
Copilot reviewed 129 out of 133 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| webllm_examples/* | Example files for WebLLM streaming and multi-round chat |
| vite.config.ts | Added proxy for Whisper API |
| src/types/shared.ts | New shared types including SupabaseError and UserProfile |
| src/store/* | Updated all stores to use robustStorage (IndexedDB) and add sync methods |
| src/services/storage/* | New repository pattern with Local and Supabase implementations |
| src/services/llmProviders/* | LLM provider abstraction supporting WebLLM and OpenAI |
| src/services/auth/* | Modular authentication service |
| src/services/journal/* | Focused journal service |
| src/services/conversation/* | Conversation/thread service |
| src/services/user/* | User profile service |
| src/services/feedback/* | Feedback/stash service |
| src/components/* | New UI components for auth, voice input, settings, clusters, reports |
| src/pages/* | New pages for clusters, reports, entries with semantic search |
| src/clients/* | Updated OpenAI client to support OpenRouter |
| package.json | Added dependencies for WebLLM, ChromaDB, localforage, etc. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| conversations: [...state.conversations, newConversation], | ||
| conversations: [...state.conversations, newConversation], |
There was a problem hiding this comment.
Duplicate line: conversations: [...state.conversations, newConversation], appears twice. This will cause a compilation error or unexpected behavior. Remove one of the duplicate lines.
| messages: state.messages.filter((m) => m.conversationId !== id), | ||
| messages: state.messages.filter((m) => m.conversationId !== id), |
There was a problem hiding this comment.
Duplicate line: messages: state.messages.filter((m) => m.conversationId !== id), appears twice. This will cause a compilation error or unexpected behavior. Remove one of the duplicate lines.
| void autoSaveMessage({ | ||
| messageId: newMessage.messageId, | ||
| sender: newMessage.sender, | ||
| text: newMessage.text, | ||
| timestamp: newMessage.timestamp, | ||
| threadId: conversationId, | ||
| timestamp: newMessage.timestamp, | ||
| threadId: conversationId, | ||
| entryId: undefined, | ||
| }); |
There was a problem hiding this comment.
Duplicate object properties: timestamp and threadId are defined twice in the autoSaveMessage call (lines 186-187). Remove the duplicate entries.
| CREATE POLICY threads_select_owner | ||
| ON public.threads | ||
| FOR SELECT | ||
| USING (auth.uid() = user_id OR is_global = TRUE); |
There was a problem hiding this comment.
The threads_select_owner RLS policy allows any caller (including unauthenticated/anon key users) to read all threads where is_global = TRUE, regardless of user_id. Because ConversationService.createThread sets is_global: !thread.entryId for a user’s general AI chats, these global threads can contain highly sensitive, user-specific conversations that become world-readable via direct Supabase/PostgREST access using the public anon key. To prevent cross-user data disclosure of private conversations, restrict this policy to auth.uid() = user_id (and any explicit sharing/organization rules) and do not use is_global alone as a read condition for untrusted clients.
| CREATE POLICY messages_select_by_thread_owner | ||
| ON public.messages | ||
| FOR SELECT | ||
| USING ( | ||
| EXISTS ( | ||
| SELECT 1 FROM public.threads t WHERE t.id = public.messages.thread_id | ||
| AND (t.user_id = auth.uid() OR t.is_global = TRUE OR EXISTS (SELECT 1 FROM public.thread_participants tp WHERE tp.thread_id = t.id AND tp.user_id = auth.uid())) | ||
| ) |
There was a problem hiding this comment.
The messages_select_by_thread_owner RLS policy permits reading any message whose parent thread has is_global = TRUE, without requiring auth.uid() to match the thread owner. Since threads.is_global is automatically set for a user’s general (non-entry) conversations, an attacker with only the public Supabase anon key can enumerate threads where is_global = TRUE and then query messages for those thread_ids, exfiltrating other users’ private AI conversations. Tighten this policy so that message access always requires ownership (t.user_id = auth.uid() and/or explicit participation) rather than relying on is_global for access control.
- Remove duplicate timestamp and threadId in autoSaveMessage call - Remove duplicate conversations in createConversation setState - Remove duplicate messages in deleteConversation setState - Fixes esbuild warnings about duplicate keys
Summary
This PR fixes the issue where journal entries were not being saved to the Supabase database after successful authentication.
Root Cause
The primary issue was that the complete database schema defined in
src/create-supabase-schema.sqlhad not been applied to the Supabase project. Without the required tables (journal_entries,users,threads, etc.), insert operations would fail silently or with errors.Changes Made
Code Fix: Date Format Correction
src/services/journal/JournalService.tscreateJournalEntryandupdateJournalEntrymethods to format thedatefield asDATEtype (YYYY-MM-DD) instead of full ISO datetime stringjournal_entries.dateasDATEtype, but code was passing full datetime stringsRequired User Action: Apply Database Schema
To complete the fix, you must apply the database schema:
src/create-supabase-schema.sqlTesting
After applying the schema:
journal_entriesdatefield is stored as a proper DATE valueTechnical Details
new Date(entry.date).toISOString().split('T')[0]to match Supabase DATE columnjournal_entriestable referencesusers(id), so both tables must existVerification Checklist
journal_entriestable exists with correct structureuserstable exists for user profilesLinks
thoughts/research/2026-02-20-1200-journal-entry-save-fix.mdthoughts/plans/2026-02-20-1200-journal-entry-save-fix.md./prs/journal-entry-fix-description.md