Skip to content

fix: journal entries not saving to Supabase database#3

Open
jeremykamber wants to merge 28 commits into
mainfrom
hotfix/journal-entries-not-saving
Open

fix: journal entries not saving to Supabase database#3
jeremykamber wants to merge 28 commits into
mainfrom
hotfix/journal-entries-not-saving

Conversation

@jeremykamber

Copy link
Copy Markdown
Owner

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.sql had 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

  • File: src/services/journal/JournalService.ts
  • Change: Modified createJournalEntry and updateJournalEntry methods to format the date field as DATE type (YYYY-MM-DD) instead of full ISO datetime string
  • Reason: Supabase schema defines journal_entries.date as DATE type, but code was passing full datetime strings

Required User Action: Apply Database Schema

To complete the fix, you must apply the database schema:

  1. Open your Supabase project dashboard
  2. Go to SQL Editor
  3. Copy the contents of src/create-supabase-schema.sql
  4. Execute the SQL to create all tables, indexes, triggers, and RLS policies

Testing

After applying the schema:

  1. Restart the application
  2. Log in with an existing or new account
  3. Create a new journal entry
  4. Verify the entry appears in Supabase Dashboard → Table Editor → journal_entries
  5. Check that the date field is stored as a proper DATE value

Technical Details

  • Date Handling: Now properly formats dates using new Date(entry.date).toISOString().split('T')[0] to match Supabase DATE column
  • Schema Dependencies: The journal_entries table references users(id), so both tables must exist
  • RLS Policies: Row Level Security ensures users can only access their own entries
  • Encryption: Continues to work as before, conditionally encrypting title/content based on user settings

Verification Checklist

  • Database schema applied successfully
  • journal_entries table exists with correct structure
  • users table exists for user profiles
  • Journal entry creation works after login
  • Date field stores as DATE type
  • No console errors during entry creation
  • Entry appears in Supabase dashboard

Links

  • Research: thoughts/research/2026-02-20-1200-journal-entry-save-fix.md
  • Plan: thoughts/plans/2026-02-20-1200-journal-entry-save-fix.md
    ./prs/journal-entry-fix-description.md

…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
Copilot AI review requested due to automatic review settings February 21, 2026 02:09
@netlify

netlify Bot commented Feb 21, 2026

Copy link
Copy Markdown

Deploy Preview for echo-journal failed. Why did it fail? →

Name Link
🔨 Latest commit c63bf4e
🔍 Latest deploy log https://app.netlify.com/projects/echo-journal/deploys/69991658e82bba0008c97f2e

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/store/conversationStore.ts Outdated
Comment on lines 225 to 226
conversations: [...state.conversations, newConversation],
conversations: [...state.conversations, newConversation],

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate line: conversations: [...state.conversations, newConversation], appears twice. This will cause a compilation error or unexpected behavior. Remove one of the duplicate lines.

Copilot uses AI. Check for mistakes.
Comment thread src/store/conversationStore.ts Outdated
Comment on lines +249 to +250
messages: state.messages.filter((m) => m.conversationId !== id),
messages: state.messages.filter((m) => m.conversationId !== id),

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +189
void autoSaveMessage({
messageId: newMessage.messageId,
sender: newMessage.sender,
text: newMessage.text,
timestamp: newMessage.timestamp,
threadId: conversationId,
timestamp: newMessage.timestamp,
threadId: conversationId,
entryId: undefined,
});

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate object properties: timestamp and threadId are defined twice in the autoSaveMessage call (lines 186-187). Remove the duplicate entries.

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +258
CREATE POLICY threads_select_owner
ON public.threads
FOR SELECT
USING (auth.uid() = user_id OR is_global = TRUE);

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +370 to +377
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()))
)

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants