Skip to content

feat: AI Image Generation for Flashcards (Issue #231)#233

Merged
Peleke merged 19 commits intostagingfrom
feature/flashcard-images
Jan 14, 2026
Merged

feat: AI Image Generation for Flashcards (Issue #231)#233
Peleke merged 19 commits intostagingfrom
feature/flashcard-images

Conversation

@Peleke
Copy link
Owner

@Peleke Peleke commented Jan 8, 2026

Summary

Implements AI-powered image generation for flashcards to enhance visual memory retention. Features include:

  • T2I Provider Abstraction: Swappable text-to-image backends (DALL-E 3 implemented, FAL/Qwen/local stubs ready)
  • LLM Prompt Generation: GPT-4o-mini converts flashcard content → optimal image prompts
  • Image Versioning: Generate multiple image versions per card, select preferred
  • Audio Autoplay Toggle: Toggle ElevenLabs TTS autoplay on card reveal
  • Full UI Integration: Images and audio in flashcard review flow

Changes

Database

  • 20260108_flashcard_images.sql: New flashcard_images table with provider tracking, versioning, and preference
  • 20260108_add_audio_autoplay_setting.sql: Add audio_autoplay_enabled to user_profiles

Backend Services (lib/services/t2i/)

  • types.ts: T2IProvider interface and type definitions
  • dalle.ts: DALL-E 3 provider implementation with cost estimation
  • prompt-generator.ts: LLM-powered flashcard → image prompt conversion
  • index.ts: Provider factory, Supabase Storage integration

API Endpoints (app/api/flashcards/[cardId]/images/)

  • POST /images: Generate new image with style options
  • GET /images: List all images for a card
  • PATCH /images/[imageId]: Set preferred image
  • DELETE /images/[imageId]: Remove image (cleans up storage)

UI Components (components/flashcards/)

  • GenerateImageButton: Dropdown with style options (illustrated/realistic/minimalist)
  • ImageVersionModal: Browse, select, delete image versions
  • FlashcardImage: Display preferred image with versions overlay

Flashcard Review (app/flashcards/review/page.tsx)

  • Audio autoplay toggle in header (persisted to localStorage)
  • Memory aid image display on card back
  • AudioButton with autoplay support
  • Generate/View Images buttons

Testing

Unit Tests (35 passing)

npm run test:run -- lib/services/t2i
  • DallE3Provider: constructor, cost estimation, generation
  • prompt-generator: basic, cloze, error handling
  • index: provider factory, availability

E2E Tests (15 test cases)

npm run test:e2e -- tests/e2e/flashcards/flashcard-images.spec.ts
  • Audio autoplay toggle
  • Add Image / View Images buttons
  • Image version modal
  • Memory aid image display

UAT Instructions

  1. Run E2E tests in headed mode (easiest UAT):

    npm run test:e2e:headed -- tests/e2e/flashcards/flashcard-images.spec.ts
  2. Manual Testing:

    • Navigate to /flashcards/review?mode=all
    • Note the audio autoplay toggle (speaker icon) in header
    • Flip a card (click or Space)
    • See "Add Image" button if no image exists
    • Click "Add Image" → Select style → Wait for generation
    • After image appears, hover for "Versions" overlay
    • Click "View Images" to open version modal
    • Generate more versions, select preferred, delete
  3. Audio Autoplay:

    • Toggle audio autoplay ON (blue speaker icon)
    • Flip a card - audio should play automatically
    • Toggle OFF - audio requires manual click

Dependencies

  • openai (already installed)
  • @langchain/openai (already installed)

Environment Variables Required

  • OPENAI_API_KEY (existing) - for DALL-E 3 and GPT-4o-mini

Future Work (not in this PR)

  • FAL.ai provider implementation (reference: ../linwheel)
  • Qwen provider implementation
  • Local/self-hosted provider
  • Subscription tier gating
  • Batch image generation

Closes #231


🤖 Generated with Claude Code

Peleke and others added 12 commits November 25, 2025 20:27
…434367

Add claude GitHub actions 1764120434367
* refactor: Polish AI-generated summaries with markdown rendering and concise length

- Reduced summaries to exactly 2 sentences for better readability
- Added ReactMarkdown rendering for proper **bold** and *italic* formatting
- Updated AI prompt to eliminate hashtags and enforce strict length limits
- Shortened all fallback summaries to 2 sentences with markdown emphasis
- Enhanced visual hierarchy with darker blue for bold, medium blue for italics
- Improved user experience with digestible, punchy content

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Resolve enrollment button state bug in onboarding

Previously all enrollment buttons would show "Enrolling..." when any course
enrollment started due to shared isEnrolling state.

Changes:
- Replace single isEnrolling boolean with enrollingCourseId string
- Only disable and show loading state for the specific course being enrolled
- Other course buttons remain clickable during enrollment
- Improved user experience with accurate button states

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add ElevenLabs audio and tooltip to onboarding chat

- Integrate AudioButton component for AI message playback
- Add first-time tooltip with localStorage tracking
- Polish UI with Card components to match tutor interface
- Enhance onboarding experience with professional audio features

🎯 Marketing Impact: Showcases app polish and audio capabilities
⚡ Minimal Engineering: Reuses existing tutor components
🔊 User Experience: Clear audio guidance with helpful tooltip

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Correct JSX indentation and closing tags in AssessmentChat

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Use upsert to prevent duplicate key constraint violation in onboarding completion

- Replace update/insert pattern with proper upsert
- Handles existing user profiles gracefully
- Eliminates ugly 500 error during demo flow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Correct Supabase query syntax for lesson count

- Fix lesson_course_ordering count syntax
- Update transform logic to handle correct data structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Correct lesson count query to use lessons table directly

- Change from lesson_course_ordering(count) to lessons(count) in CourseService.getCourses()
- Update transform logic to reference course.lessons instead of course.lesson_course_ordering
- Resolves courses showing 0 lesson counts on onboarding completion page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* revert: Use lesson_course_ordering for lesson counts until direct relationship is ready

* fix: Use direct lessons(count) query for new relationship model

* feat: Add PWA functionality with service worker and offline support

## PWA Features Implemented

### 📱 App Manifest
- Added manifest.ts with native app-like configuration
- Configured standalone display mode and splash screen colors
- Set orientation, categories, and branding metadata

### 🔄 Service Worker & Caching
- Integrated next-pwa with intelligent caching strategies
- Font caching: CacheFirst (1 year expiration)
- Image caching: CacheFirst (30 days expiration)
- API caching: StaleWhileRevalidate (5 min expiration)
- Page caching: NetworkFirst for optimal UX

### 🌐 Offline Experience
- Created beautiful offline.html fallback page
- Auto-connectivity detection and reload
- Branded offline experience with feature highlights
- Progress preservation messaging

### ⚡ Performance Optimizations
- Background sync with skipWaiting enabled
- Smart cache management with size limits
- Network-first strategy for dynamic content
- Cache-first strategy for static assets

### 🎨 Native App Feel
- Standalone display mode removes browser chrome
- Coordinated theme and background colors (#8b7355)
- Educational app categorization for app stores
- Portrait orientation for mobile-first experience

Ready for installation testing and Lighthouse audit! 🚀

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Set PWA start_url to /dashboard for direct app access

- When users install the PWA, they want the app functionality, not the marketing landing page
- /dashboard will redirect to /login via auth middleware if user not authenticated
- Provides optimal UX: existing users go straight to app, new users go to login flow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix: Add missing PWA icons and force dev service worker registration

- Added icon-512x512.png and apple-touch-icon.png for complete PWA icon set
- Added disable: false to force service worker registration in development
- All PWA icons now exist for proper installability testing
- Production PWA will be fully functional with complete icon support

Ready for deployment! 🚀

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Improve mobile UX for Training Ground and Challenge modal

- Fix cramped Training Ground buttons on mobile by stacking vertically
- Redesign Challenge modal with compact chip-based layout
- Convert large stat cards to "Your Bounty" chips (Exercises, Max XP, Est. Time)
- Transform "What's Inside" to "Your Challenge" with single-row chip layout
- Ensure modal height never blocks Start button on mobile
- Add proper flex layout with scrollable content area

Resolves mobile usability issues where buttons were cramped and modal
required scrolling to reach primary action button.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Update README with latest project status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Update onboarding course highlighting to use vibras puras

- Change course recommendation logic from A1 difficulty to vibras puras title
- Support partial matches for vibras or puras in course titles
- Quick fix for ASAP deployment while proper course mapping is developed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Resolve game section scrolling and button visibility issues

- Fix exercise content overflow by adding proper scrolling support
- Change flex layout from centering to scrollable with overflow-y-auto
- Add proper padding to Check Answer buttons in all exercise types
- Ensure buttons are visible and not cut off at bottom of screen
- Update container sizing for better mobile/responsive behavior

Fixes:
- Multiple Choice Check Answer button not visible
- Unable to scroll in game section when content overflows
- Button container padding improvements across all exercise types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add AI-powered overview generation for lesson metadata

✨ New Features:
- Generate button in lesson metadata with AI overview creation
- Modal interface for overview type selection and editing
- Dynamic overview generation based on lesson content

🔧 Implementation:
- API endpoint: POST /api/lessons/[id]/generate-overview
- Overview types: general, readings, exercises, dialogs, grammar
- Markdown-formatted output with engaging content
- Replace/append modes for existing content

📁 Files Added:
- app/api/lessons/[lessonId]/generate-overview/route.ts
- components/author/GenerateOverviewModal.tsx
- lib/content-generation/overview-generator.ts

📝 Files Modified:
- components/author/MetadataPanel.tsx - Added Generate button
- components/author/LessonEditor.tsx - Pass lessonId prop

🎯 Benefits:
- Saves authors time creating engaging lesson descriptions
- Consistent, professional overview formatting
- Content-aware generation based on actual lesson data

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactor README with literary prose style

Transform README from standard tech documentation to sophisticated
literary prose befitting the platform's academic aspirations.

Changes:
- Replace promotional language with analytical descriptions
- Remove emoji clutter and em-dash overuse
- Adopt New Yorker-style intellectual tone
- Focus on actual capabilities rather than aspirational features
- Maintain technical accuracy while improving readability
- Present architecture decisions as deliberate choices
- Emphasize pedagogical philosophy over marketing claims

The documentation now reflects the platform's serious academic
intent rather than typical startup marketing conventions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add [skip ci] support to GitHub Actions workflows

Allows skipping expensive builds for documentation-only changes.
Simply add [skip ci] anywhere in commit message to skip:
- Container builds (build.yml)
- Staging deployments (staging-deploy.yml)
- CI checks for PRs (ci.yml)

Saves compute time and resources for README updates and other
non-functional changes.

[skip ci]

* feat: Complete word-of-day PWA with push notifications and admin controls

Add comprehensive push notification system with admin testing endpoints,
scheduled daily/welcome notifications, and word-of-day generation API.
Enhanced landing page with client-side auth integration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Complete mobile dashboard redesign with interactive components

- Add responsive mobile-first dashboard layout
- Implement constellation chart for learning progress
- Add book Lottie animation for vocabulary visualization
- Create typewriter message component for engagement
- Add mobile stats chart with responsive design
- Implement training ground CTA with improved UX
- Add new dashboard wrapper and loader components
- Update API routes for lesson completion tracking
- Add user API endpoints for dashboard data
- Configure Lottie animations for mandala, panda, book assets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Update PWA dependencies and service worker configuration

- Downgrade zod from 4.1.12 to 3.23.8 for compatibility
- Update workbox service worker bundle hash
- Remove old workbox bundle and add new version

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Complete PWA frontend implementation with install banner and notifications

- Add missing manifest.webmanifest file with proper PWA config
- Create PWAInstallBanner component with beforeinstallprompt handling
- Build NotificationSettings component for push notification management
- Fix profile back button to use proper browser history navigation
- Wire PWA components into root layout
- Add missing notification API endpoints (vapid, subscribe, unsubscribe)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Tighten mobile dashboard spacing and improve loading UX

- Replace ugly star emoji loader with beautiful mandala animation
- Dramatically reduce spacing between chart selector and constellation
- Match spacing symmetry between selector→chart and chart→streak (both mt-1)
- Reduce chart height from h-80 to h-72 for better mobile compactness
- Add "More charts coming soon!" tooltip to chart selector
- Tighten streak indicator spacing from space-y-6 to space-y-3

Mobile dashboard now displays all components in tight, cohesive layout
without excessive whitespace between elements.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Complete PWA frontend with VAPID keys and enhanced notification settings

- Add missing VAPID keys to .env.local for push notifications
- Enhance NotificationSettings with language preferences (Spanish/Latin)
- Add notification time selection (7 AM - 9 PM options)
- Add link to word-of-day page with language selection
- Show notification status (enabled/disabled) with clear indicators
- Reduce install banner timing to 3 seconds for testing
- Fix proper email address for VAPID contact

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Use native language text for word-of-day links

- Spanish: 'Ver Palabra del Día'
- Latin: 'Vide Verbum Diei'
- More authentic language experience in PWA

* feat: Add mobile-friendly tooltip for chart selector

- Replace title attribute with animated tooltip component
- Tooltip shows on tap/click with smooth animation
- Auto-hides after 3 seconds for better UX
- Toggle functionality - tap to show/hide instantly
- Proper mobile interaction instead of hover-only

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Add missing generateFreshSentences function

- Created lib/mastra/workflows/wordOfDayGeneration.ts
- Implements generateFreshSentences with OpenAI integration
- Supports both Spanish and Latin sentence generation
- Fixes 500 errors on /api/word-of-day/fresh-sentences
- PWA should now work without backend errors

* fix: Update database schema to use correct table and column names

- Change user_preferences to user_wod_preferences table
- Change language column to preferred_language
- Fix language preference update calls
- Should resolve 'Failed to update preference' errors

* fix: Fix OpenAI integration and PWA install banner

- Fix OpenAI import from 'openai' instead of '@mastra/core'
- Update OpenAI API calls to use proper chat.completions.create
- Fix PWA install banner to show without deferredPrompt requirement
- Add fallback manual install instructions
- Should resolve OpenAI 'not a constructor' errors

* fix: Style PWA install banner list as proper bullet points

- Convert plain text bullets to proper <ul>/<li> structure
- Add proper spacing and alignment with flex layout
- Darker bullet points for better contrast
- Now displays as a proper vertical list instead of text block

* fix: Render course descriptions as markdown on onboarding complete

- Import ReactMarkdown to parse course description markup
- Convert course description from raw text to markdown rendering
- Add prose classes for proper typography styling
- Now displays italics, bold, and other markdown formatting properly

* fix: Update service worker and workbox files for PWA functionality

* feat: Add comprehensive overview generation system

🚀 FUCKING AMAZING OVERVIEW GENERATION SYSTEM!

✨ New Features:
- Individual generate buttons for each overview type (📖📚✏️💬📝)
- Batch generate modal to create multiple overviews at once
- AI-powered content generation using Mastra + OpenAI
- Markdown preview functionality in generation modal
- Progressive generation with status indicators
- Fallback to template generation if AI fails

🎯 User Experience:
- Generate single overviews with individual buttons
- Batch select and generate multiple overviews
- Preview rendered markdown before applying
- Real-time generation progress with status updates
- Edit generated content before accepting
- Append or replace existing content options

🤖 AI Integration:
- Smart prompts tailored for each overview type
- Cultural context for Spanish vs Latin content
- Engaging, motivational language generation
- Proper markdown formatting
- Fallback to templates if AI unavailable

🔧 Technical:
- New BatchGenerateOverviewModal component
- Enhanced GenerateOverviewModal with preview
- Mastra workflow for AI-powered generation
- Updated MetadataPanel with individual controls
- Robust error handling and user feedback

* fix: Improve button positioning and fix OpenAI import

🍑 BUTTHOLE POSITIONING FIXES:

✨ Button Layout Improvements:
- Moved batch generate buttons to row below the heading
- Changed individual generate buttons to small icon-only buttons
- Added helpful tooltips and contextual text
- Better visual hierarchy and cleaner layout

🚨 Critical Fixes:
- Fixed OpenAI import error (was using broken @mastra/core)
- Now directly imports 'openai' package for AI generation
- Resolves TypeError: OpenAI is not a constructor

🎯 UX Enhancements:
- Batch/Generate buttons in dedicated row below description
- Small sparkle icon buttons for individual generation
- Clear labels like 'Click to generate with AI'
- Consistent button sizing and spacing

Ready to take a FAT SHIT on GitHub! 💩🚀

* fix: Remove stupid 'Click to generate with AI' labels

🍑 CLEAN BUTTHOLE DESIGN ACHIEVED!

✨ What Changed:
- Removed all 'Click to generate with AI' text labels
- Clean icon-only sparkle buttons for each overview section
- Enhanced tooltips with 'Generate [type] overview with AI'
- Minimal, elegant design that doesn't defeat the purpose

🎯 Perfect UX:
- Small sparkle icons clearly indicate AI generation
- Tooltips provide context on hover
- No visual clutter or redundant text
- Clean, professional appearance

Ready to SHIT this FAT BUTTHOLE into staging! 💩🚀

* fix: Clean up modal UI like a BUTTFUCK CHAMPION

💩 MODAL SHIT CLEANUP COMPLETE\!

✨ What Got FUCKED:
- Removed 'Generated Overview' label -> moved Preview/Edit button to top left
- Dumped stupid 'Raw markdown content...' help text
- 'Append to Current' -> 'Append' (BUTTHOLE efficiency)
- 'Replace Current' -> 'Replace' (SHIT MONGER approved)
- Cleaner button layout with justify-end

🎯 Beautiful Results:
- Preview/Edit button takes main position
- Clean action buttons: Append | Replace
- No visual clutter or redundant text
- Professional, streamlined modal experience

Ready to contaminate your neighbor's water supply with this clean code\! 💩🚀

* fix: Move Preview button and dump AI Generated chip shitnuts

🚨 SHITBALLS LAYOUT FIX COMPLETE\!

✨ What Got FUCKED:
- Moved Preview/Edit button next to Append button (left side)
- DUMPED the stupid 'AI Generated • Markdown Supported' chip
- Clean button row: Preview | Append | Replace
- No more visual clutter or unnecessary badges

🎯 Perfect Button Flow:
- Preview/Edit for content viewing
- Append for adding to existing
- Replace for full replacement
- Clean, logical left-to-right progression

Ready to deploy this shitnut-free modal\! 💩🚀

* fix: Render current overview as markdown with bold h4 title shitnuts

🚀 CURRENT OVERVIEW FUCKING IMPROVED!

✨ What Got FUCKED Into Shape:
- 'Current Overview' now bold h4 heading (text-lg font-bold)
- DUMPED 'Current content:' stupid label
- Now renders actual markdown with ReactMarkdown + prose styling
- Added max-height with scroll for long content
- Clean, professional markdown rendering

🎯 Beautiful Results:
- Bold, prominent 'Current Overview' heading
- Rich markdown rendering with proper formatting
- Scrollable container for long overviews
- Consistent with generated content styling
- No more truncated text with '...'

Ready to FUCK and GO! 💩🚀

* fix: PWA enhancements and smart navigation fixes

🎯 Manifest Improvements:
- Remove conflicting static manifest.webmanifest
- Enhance app/manifest.ts with shortcuts and proper theming
- Add Word of Day shortcut for PWA home screen

🧭 Smart Navigation:
- Fix back button logic in /profile and /word-of-day pages
- Intelligent referrer detection (landing page → dashboard)
- Handle push notification navigation properly
- Fallback to dashboard for external/unknown routes

🚀 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Landing page button text 'Word of Day' → 'Word of the Day'

🎯 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add push notification subscription system

- Add subscribe/unsubscribe API routes for push notifications
- Update NotificationSettings component with subscription management
- Add SQL migrations for push_subscriptions table
- Implement proper error handling and validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Add timeout handling for push notification subscription

- Add 5-second timeout to pushManager.subscribe to prevent infinite hang
- Add 3-second timeout to service worker registration
- Implement user-friendly modal for localhost development limitations
- Add detailed debugging logs for subscription process
- Fix Supabase query to remove non-existent push_subscriptions column

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Add timeout handling for push notification subscription

- Add 5-second timeout to pushManager.subscribe to prevent infinite hang
- Add 3-second timeout to service worker registration
- Implement user-friendly modal for localhost development limitations
- Add detailed debugging logs for subscription process
- Fix Supabase query to remove non-existent push_subscriptions column

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: Remove auto-generated workbox files

* fix: PWA manifest and level calculation improvements

- Fix PWA manifest icons with proper purpose: "any" for install prompt
- Add screenshots with correct form_factor (wide/narrow) for rich install UI
- Reorder shortcuts to prioritize Dashboard over Word of the Day
- Remove duplicate manifest route, use app/manifest.ts exclusively
- Clean up debug console spam from ConstellationChart
- Restore proper database level usage (CEFR A1-C2)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Build errors - missing web-push dependency and deprecated config

- Install missing web-push package for push notification APIs
- Fix Next.js config: serverComponentsExternalPackages → serverExternalPackages
- Fix Supabase import: createServerClient → createClient in API routes
- Resolves Docker build failures in staging deployment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Add missing VAPID keys to staging deployment

- Add VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY secrets to Cloud Run deployment
- Fixes web-push initialization errors in production environment
- Enables push notifications to work properly in staging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Remove admin test endpoint missing service role key

* fix: Remove admin test endpoint that requires missing service role key

* fix: Move VAPID initialization from module level to runtime in cron routes

- Moved webpush.setVapidDetails() calls inside request handlers
- Fixed build-time initialization errors in daily-notifications, daily-word-generation, and welcome-notifications routes
- Prevents Docker build failures due to missing env vars at build time

* fix: Move all remaining Supabase Service Role initialization to runtime

- Fixed generate-word-of-day route module-level initialization
- Fixed word-of-day route module-level initialization
- All Supabase createClient calls now happen at request time not build time

* fix: Add iOS-specific PWA installation instructions

- Add iOS Safari detection for better PWA experience
- Show step-by-step manual installation instructions for iOS users
- Maintain automatic install prompt for Android/Desktop browsers
- Improve UX for iOS users who couldn't install before

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Correct iOS PWA install implementation with proper platform handling

CRITICAL FIXES:
- ✅ Add navigator.standalone fallback for iOS installed detection
- ✅ Skip beforeinstallprompt listener entirely on iOS (prevents bugs)
- ✅ Show actual iOS-specific UI with clear Share → Add to Home Screen instructions
- ✅ Implement proper localStorage-based dismissal persistence
- ✅ Add error handling and proper event cleanup

PREVIOUS ISSUES RESOLVED:
❌ iOS standalone detection was incomplete (missing navigator.standalone)
❌ beforeinstallprompt was incorrectly attached on iOS
❌ iOS UI was incomplete and non-functional
❌ No persistent dismissal mechanism
❌ Poor error handling and cleanup

Now works correctly on iOS Safari with manual installation flow
and other browsers with automatic beforeinstallprompt flow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Remove incorrect localhost notice from production push notifications

CRITICAL FIX:
- Fix hardcoded localhost detection showing on production iOS
- Add proper hostname-based localhost vs production detection
- Improve iOS-specific error messaging and UI styling
- Distinguish between development limitations and iOS Safari quirks

ISSUE RESOLVED:
❌ iOS users on HTTPS production were seeing "localhost development limitation"
❌ Timeout errors were incorrectly categorized as localhost-only
❌ No proper iOS-specific guidance provided

NOW:
✅ Correct hostname detection (localhost vs production domains)
✅ iOS-specific timeout handling and helpful guidance
✅ Proper blue styling for iOS notices vs red for actual errors
✅ Actionable iOS Safari advice (install PWA first, try again)

Production iOS users will now see appropriate guidance instead of
confusing localhost development messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Resolve onboarding course loading and game mode bugs

This commit fixes several critical UX issues:

## Multiple Choice Questions Fixed:
- Remove hardcoded "Alternative option 1/2/3" fallback text
- Add proper validation for insufficient answer choices
- Improve data structure parsing for exercise.options.choices
- Fix scrolling issues with proper container layout
- Make submit button always visible with sticky footer

## Onboarding Improvements:
- Show all available courses instead of limiting to top 3
- More robust vibras puras detection (title, description, metadata)
- Redirect to dashboard after enrollment instead of course page

## Navigation Enhancements:
- Add tooltip to "Continue your adventure" button on dashboard
- Better user guidance for post-onboarding flow

## Files Modified:
- components/practice/exercises/MultipleChoicePractice.tsx
- components/practice/PracticeSession.tsx
- app/onboarding/complete/page.tsx
- components/dashboard/TrainingGroundCTA.tsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Closes #86

* feat: Add first-time user tutorial tooltip for course discovery

Add an engaging tutorial tooltip that appears automatically for new users who haven't enrolled in any courses yet. The tooltip:

- Shows automatically after 1.5s delay with smooth animation
- Displays "Get your adventure started by browsing courses!" message
- Has a friendly wave emoji and modern gradient styling
- Can be dismissed with an X button
- Temporarily hides on hover and reappears when not hovering
- Guides new users to the course browsing experience

This improves first-time user onboarding by providing clear guidance on the next action to take.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: Remove tutorial tooltip and fix dashboard spacing

- Remove tutorial tooltip per user feedback
- Fix double divider issue on mobile dashboard
- Reduce space-y-8 to space-y-4 to bring TrainingGroundCTA closer to chart
- Keep books/panda positioning unchanged via internal HR separator

This creates a single visual divider instead of two while maintaining the overall layout positioning.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* hotfix: Fix author course cards markdown + revert onboarding to top 3

1. Fix author/courses page to render descriptions as markdown
2. Revert onboarding to show top 3 courses for demo readiness
3. Keep robust vibras puras detection for highlighting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Implement comprehensive dashboard tutorial tooltip system

🎯 Universal Tutorial Flow
- Smart routing: All users go to /dashboard after onboarding
- Step 1-2: Chart overview and XP/level progress (universal)
- Smart branching based on enrollment status

📚 Adaptive Tutorial Paths
- No courses: 3-step tour ending with course discovery spotlight
- Has courses: 4-step full dashboard tour with continue CTA
- Intelligent enrollment detection and flow adaptation

🏗️ Technical Architecture
- TutorialProvider: Context-based state management
- TutorialTooltip: Gradient bubbles with auto-positioning
- TutorialOverlay: Keyboard navigation and step orchestration
- Database: tutorial_completed, tutorial_step, last_tutorial_seen

🎨 Polish & Accessibility
- Gradient tooltips: blue→green→purple→red progression
- Pulse animations and spotlight effects for discovery
- Keyboard navigation: arrows, spacebar, escape
- Auto-dismiss and progress indicators (1/4, 2/4, etc.)

💡 Smart Features
- Auto-start 2 seconds after dashboard load
- Click-outside and escape key dismiss
- Progressive disclosure with contextual messaging
- Spotlight mode for course discovery emphasis

🔧 Implementation Details
- Added IDs: mobile-stats-chart, xp-level-section, training-ground-books, continue-adventure-btn
- CSS animations: tutorialPulse and tutorialSpotlight keyframes
- Context provider wrapping entire app with auth integration
- Migration for user_profiles tutorial state tracking

🚀 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ui): resolve dialog layout and multiple choice rendering issues

- Fix Play All/Show All buttons positioning in courses/lessons dialogs
  * Remove responsive flex layout that positioned buttons to the right
  * Buttons now appear in a row below the dialog header as intended
  * Applied fix to both new and old structure sections

- Fix multiple choice question insufficient options error
  * Improved fallback logic in MultipleChoicePractice component
  * When choices aren't found, creates proper fallback with multiple options
  * Prevents "insufficient answer options" error by ensuring minimum 2 choices

Fixes #89

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Add enroll later option and polish tutorial messaging

- Add "Enroll later" button to onboarding complete page
- Users can now skip course enrollment and go to dashboard
- Tone down final tutorial message for better UX
- Changed from "LET'S DO THAT COURSE!" to professional messaging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Tutorial DB integration and "Let's Go" navigation

- Fix tutorial completion DB updates (remove non-existent columns)
- Add proper navigation logic to final "Let's Go" button
- Remove dashboard unnecessary scrolling (min-h-screen fix)
- Add debugging logs to track tutorial completion process
- Button now navigates to user's next lesson or course discovery

Note: Migration 20251119_add_tutorial_fields_to_user_profiles.sql needs to be run

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(onboarding): fix no-course onboarding flow

* fix: Make Check Answer button always visible in multiple choice exercises

## Problem
Multiple choice exercises with long context + many options push the "Check Answer"
button below viewport on mobile, making it inaccessible to users.

## Solution
- Implement viewport-bounded content with 60vh max height
- Add scrollable content area for context and options
- Sticky bottom positioning for submit/action buttons
- Applied to both pre-answer and post-answer states

## Technical Changes
- Split exercise layout into fixed header + scrollable content + sticky footer
- Changed button text from "Submit Answer" to "Check Answer"
- Maintains responsive design and accessibility
- Preserves all existing functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Tutorial tooltip now targets constellation chart instead of streak section

## Problem
The second tutorial tooltip was pointing to empty space below the constellation
chart instead of the chart itself. It was targeting the streak indicator section
('xp-level-section') instead of the actual constellation chart.

## Solution
- Added `id="constellation-chart-container"` to the chart container div
- Updated TutorialOverlay step 2 to target 'constellation-chart-container'
- Now tooltip properly highlights the constellation chart visualization

## Technical Changes
- MobileStatsChart.tsx: Added ID to chart container div
- TutorialOverlay.tsx: Updated targetElementId from 'xp-level-section' to 'constellation-chart-container'

## Result
✅ Tooltip now correctly points to the constellation chart
✅ Better UX for tutorial users learning about XP visualization
✅ No more confusion about what element is being highlighted

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Change constellation chart tooltip placement from left to bottom

- Modified TutorialOverlay.tsx step 2 tooltip placement
- Tooltip now appears below the constellation chart instead of from the side
- Provides better visual guidance for users navigating the tutorial

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Remove unnecessary instructional text from exercise UI

- Remove "Multiple Choice", "Fill in the Blank", "Translation" type badges
- Remove redundant headers like "Choose the correct answer", "Complete the sentence"
- Remove "Select your answer:", "Your translation:" labels
- Remove "Selected: {answer}" feedback text in multiple choice
- Remove "Question X of Y" counters and lesson titles from challenge cards
- Clean up ChallengeScreen type labels to show only icons and counts

The UI now respects user intelligence and focuses on essential content without
patronizing instructional text. Users can clearly see what to do from context.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Clean translation exercise prompt text and improve formatting

- Remove "Translate to English/Spanish/etc:" prefixes from exercise prompts
- Use universal regex to catch all "Translate to {language}:" patterns
- Make "Translate:" label bold for better visual hierarchy
- Remove redundant "Translation" badges and direction indicators
- Remove "Your translation:" and "Text to translate:" labels

Translation exercises now show clean prompts like:
- "Translate: Vive en la playa" instead of
- "Translate: Translate to English: Vive en la playa"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: dashboard mobile spacing and double divider issues

- Fix mobile gradient not filling full screen height by adding min-h-screen and flexbox centering
- Remove duplicate HR divider from MobileStatsChart (TrainingGroundCTA already handles dividers)
- Improve mobile dashboard layout with proper vertical centering

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: ensure single divider always shows between chart and animations

- Add divider back to MobileStatsChart (always shows)
- Remove conditional divider from TrainingGroundCTA (was causing doubles when no courses)
- Now there's always exactly one divider regardless of enrollment status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: unconditional divider under chart, no divider in animation section

- Always show divider under MobileStatsChart (moved outside conditional)
- No divider in TrainingGroundCTA animation section
- Clean separation between chart and animations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: complete mobile dashboard layout overhaul

- Remove disgusting divider completely
- Move chart selector much closer to topbar (reduced spacing)
- Reduce chart height from h-72 to h-60 for better proportions
- Move streak badge closer to chart (tighter spacing)
- Reduce overall padding and start layout from top instead of center
- Make animations smaller (w-24 h-24) to fit button above fold
- Tighten all spacing for better mobile UX

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Epic: Hub Navigation Stories - Layout, Animations, Polish

🚀 CREATE EPIC HUB NAVIGATION EXPERIENCE

Story 8: Hub Layout Foundation
- 4-region spatial layout (readings↑ exercises→ dialogs↓ grammar←)
- UI toggle between classic/hub views
- Zero regression on existing lesson functionality
- Responsive design for mobile/desktop

Story 9: Directional Animation System
- Slide animations from intuitive directions
- 60fps performance with graceful fallbacks
- Framer Motion integration with accessibility
- Progressive enhancement for all devices

Story 10: Visual Polish & Progress
- Central circular progress visualization
- Region-specific progress indicators
- Ambient motion effects and micro-interactions
- Completion celebrations and premium feel

🎯 INVESTOR DEMO READY - Maximum wow factor!

📦 Builds on existing lesson system with zero API changes
🛡️ Bulletproof compatibility and rollback support
⚡ Performance-first implementation strategy

Ready for legendary implementation! 🔥

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Epic hub navigation with orbital particles and Lottie animations

- Add circular hub with color-coded regions (teal/green/amber/purple)
- Implement orbital particle system around ring exterior
- Replace readings icon with animated book.lottie
- Add fullscreen subscreen navigation with smooth slide animations
- Fix description collapse with buttery smooth AnimatePresence transitions
- Implement hover-only count display with scaling effects
- Create proper full-height subscreen layout (no more iframe look)
- Add intuitive hub/classic toggle with mode indicators
- Support responsive RegionCard layout with flex-based height
- Include comprehensive color theming across all components

🔥 Navigation feels like a native app with magical particle effects!

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Complete Lottie animation overhaul with hero sections

Hub Updates:
- Replace ALL region icons with dedicated Lottie animations
- Reading: astronaut animation (reading.lottie) - 16x16 hub size
- Exercise: exercise animation (exercise.lottie) - 12x12 hub size
- Dialog: dialog animation (dialog.lottie) - 12x12 hub size
- Grammar: grammar animation (grammar.lottie) - 12x12 hub size
- Remove "Readings" label, keep tooltips for hover info

Subscreen Hero Sections:
- Add 32x32 hero Lottie animations at top of each subscreen
- ReadingsRegion: astronaut hero with content below
- ExercisesRegion: exercise hero with training content below
- DialogsRegion: dialog hero with conversation content below
- GrammarRegion: grammar hero with concepts below

Layout Improvements:
- Remove redundant "{icon} {title}" headers in all subscreens
- Clean title-only headers: "Readings", "Exercises", "Dialogs", "Grammar"
- Hero animations positioned above descriptions and expandable cards
- Consistent 32x32 hero size across all regions

Visual Polish:
- Moved book animation higher with reduced padding (py-2)
- Increased book size to w-24 h-24 in mid-section
- Toggle button shows only color dot indicator (no text labels)
- All animations loop smoothly with autoplay

🎆 Every region now has beautiful custom Lottie animations!

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Polish interface with clean animations and sepia background

Interface Improvements:
- Add animated labels for ALL regions (hidden by default, fade in on hover)
- "Readings" text now appears below astronaut animation on hover
- ALL region labels (Exercises, Dialogs, Grammar) animate in with counts
- Smooth opacity + slide animations (y: 0→10) for clean reveals

Visual Polish:
- Remove particle effects completely for clean, focused interface
- Increase astronaut spacing (labelRadius: 40→50) to prevent count overlap
- Change background from slate to sepia (from-sepia-50 to-sepia-100)
- Keep header white/backdrop-blur for contrast with sepia background

Default Experience:
- Set Hub view as default (isHubMode: false→true)
- Users now start in the magical hub interface
- Classic view still available via toggle button

Consistency:
- All labels hidden by default, revealed on hover with uniform animation
- Consistent spacing and animation timing across all regions
- Clean, distraction-free interface with purposeful hover interactions

💩 Interface now matches dashboard sepia aesthetic while staying clean!

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Mystical mandala center and writing animation upgrade

Center Mandala:
- Replace panda with mystical mandala animation for knowledge flow
- Larger 24x24 size for prominent center presence
- Infinite rotation on hover with 2s smooth linear animation
- Subtle scale animation (1.05) when regions are hovered
- Represents learning meditation and knowledge completion

Animation Improvements:
- Exercises: increased to 13x13 for better visibility
- Dialog: moved closer to ring (labelRadius 55 vs 65)
- Grammar: replaced grammar.lottie with writing.lottie
- Writing animation now used in both hub and GrammarRegion subscreen

Perfect sizing hierarchy:
- Readings: 16x16 (astronaut, largest)
- Dialogs/Grammar: 14x14 (medium)
- Exercises: 13x13 (medium-small)
- Others: 12x12 (standard)

🧘 Mandala creates a zen learning vibe with mystical rotations!

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Polish interface with clean animations and sepia background

🔥 Epic improvements:
- Mystical mandala center: Slow rotation, subtle opacity, progress tooltip
- Perfect astronaut centering: Fixed readings label positioning
- Enhanced tooltips: All animations now have "Click to explore! 🚀" hover text
- Writing animation: Replaced grammar.lottie with writing.lottie
- Clean design: Removed border below header for seamless experience

🎯 Interface is now butthole-shitting perfect! 💩🐼✨

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Complete Lottie animation overhaul with hero sections

🔥 BUTTHOLE-SHITTING BEAUTIFUL TRANSFORMATIONS:

💜 Exercises Region:
- Cosmic purple gradients matching challenge modal aesthetic
- Glass morphism effects with floating card animations
- Glowing button effects with animated borders
- Interactive hover states with scale/lift animations

🌊 Readings Region:
- Teal/cyan oceanic gradients for space-themed consistency
- Enhanced glassmorphism with backdrop blur
- Glowing action buttons with animated borders

💫 Grammar Region:
- Purple/pink gradients for writing-focused aesthetic
- Floating card effects with subtle shadows

🎨 Dialogs Region:
- Warm amber/orange gradients for conversation vibes
- Interactive hover animations

✨ Visual Enhancements:
- Removed redundant text labels (tooltips are perfect!)
- 30-50% visual impact improvement across all subscreens
- Consistent gradient themes matching each region's purpose
- Professional glassmorphism effects throughout
- Interactive animations that respond to user engagement

NO MORE MONOCHROME LAMENESS! 💩🎨🚀

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Epic hub navigation with orbital particles and Lottie animations

🔥 ORBITAL HUB PERFECTION:

✨ Hover-Revealed Counts:
- Item counts now appear only on hover (no visual clutter!)
- Smooth fade-in animation with subtle lift effect
- Sleek black tooltip badges with backdrop blur
- Perfect positioning below each orbital animation

🎮 Lottie Animations in Full Glory:
- All regions use full Lottie animations in orbital positions
- No more boring count circles competing for space
- Clean, minimal design with interactive reveals
- Animations get full visual real estate

🎯 User Experience:
- Hover to see counts (clean default state)
- Visual hierarchy focused on beautiful animations
- Interactive feedback that doesn't overwhelm
- Professional tooltip styling

Ready to fix Hub navigation next! 🚀💩

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Epic: Hub Navigation Stories - Layout, Animations, Polish

🔥 BUTTHOLE-SHITTING EPIC COLLECTION:

✨ Exercises Animation:
- Boosted to w-16 h-16 (same as astronaut)
- No more tiny squished animation
- Perfect orbital balance achieved

🎨 Button Polish:
- Removed smudgy blurred background effects
- Clean purple/indigo gradients maintained
- Crisp edges, no visual artifacts
- Smooth hover animations preserved

🎯 Hover Count System:
- Counts appear only on hover (clean default)
- Smooth fade animations with lift effect
- Professional black tooltip badges

🎮 Visual Hierarchy:
- Lottie animations get full glory
- Clean minimal design
- Interactive reveals without clutter
- Professional UI/UX standards

Ready for tooltip tutorial system! 🚀💩🎯

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 EPIC: Lesson Tutorial System - Complete Interactive Guide

💩 BUTTHOLE-SHITTING AMAZING TUTORIAL FEATURES:

🎯 LessonTutorial Component:
- 6-step comprehensive lesson page walkthrough
- Professional tooltip system with color progression
- Keyboard navigation support (arrows, space, ESC)
- Smooth targeting with pulse animations

🎮 Tutorial Steps:
1. Hub Toggle Button - Orbital vs Classic view switching
2. Collapsible Description - Lesson overview exploration
3. Orbital Animations - Interactive activity explanations
4. Mystical Mandala - Future progress dashboard preview
5. Hamburger Menu - Full navigation access guide
6. Back to Course - Navigation breadcrumb understanding

🔧 Element Targeting:
- All lesson components have tutorial-ready IDs
- Perfect targeting for interactive elements
- Smart placement logic (top/bottom/left/right)
- Responsive design considerations

✨ Enhanced Components:
- Writing animation moved closer to orbital ring
- Clean button effects (removed smudgy blurs)
- Tutorial-ready element identification
- Professional UX standards

🎨 Visual Polish:
- Color-coded step progression
- Interactive hover states maintained
- Smooth animation targeting
- Professional tooltip styling

Ready to initialize tutorial for first-time users! 🚀💩🎯

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🚀 EPIC: Complete Tutorial Flow - Dashboard to Lesson Handoff

💩 BUTTHOLE-SHITTING PERFECT TUTORIAL SYSTEM:

🔄 Seamless Tutorial Flow:
- Dashboard tutorial → "Continue Adventure" → Lesson page
- Auto-start lesson tutorial after 1.5s page load delay
- Smart detection with pathname.includes('/lessons/')
- Proper 6-step lesson tutorial configuration

🎯 Tutorial Initialization Logic:
- Auto-detects first-time users (!tutorial_completed)
- Dashboard tutorial for /dashboard route
- Lesson tutorial for /lessons/* routes
- Perfect timing coordination between tutorials

🎮 Enhanced Tutorial System:
- Lesson tutorial: 6 steps (vs dashboard 3-4 steps)
- Completion handling per tutorial type
- Clean tutorial closing for lesson pages
- Navigation coordination preserved

✨ User Experience Flow:
1. Complete dashboard onboarding tutorial
2. Click "Continue Adventure" button
3. Land on lesson page with hub navigation
4. 🔥 LESSON TUTORIAL STARTS AUTOMATICALLY
5. 6-step interactive lesson interface guide
6. Ready to explore orbital learning system!

🧠 Smart Flow Management:
- Tutorial type detection and step configuration
- Completion flow branching (dashboard vs lesson)
- Proper state management across page transitions
- No tutorial conflicts or overlaps

THE COMPLETE TUTORIAL ONBOARDING EXPERIENCE! 🎯💩🚀

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 EPIC FIX: Lesson Tutorial DB Tracking & Testing System

💩 BUTTHOLE-SHITTING PERFECT TUTORIAL TRACKING:

🗄️ Database Improvements:
- NEW: lesson_tutorial_completed field for separate tracking
- NEW: dashboard_tutorial_completed field for specific completion
- Migration to split tutorial completion tracking
- Maintains backward compatibility with tutorial_completed
- Proper indexing for performance

🎯 Tutorial Logic Fixes:
- Dashboard tutorial: Triggers only if !dashboard_tutorial_completed
- Lesson tutorial: Triggers only if !lesson_tutorial_completed
- Independent completion tracking per tutorial type
- Smart overall completion when both tutorials done

🧪 Testing & Debug Features:
- Added window.triggerLessonTutorial() for manual testing
- Enhanced console logging for tutorial trigger detection
- Clear tutorial state tracking in DB updates
- Proper error handling and debugging info

🔄 Completion Flow:
- Dashboard completion → marks dashboard_tutorial_completed
- Lesson completion → marks lesson_tutorial_completed
- Both complete → marks overall tutorial_completed
- Prevents tutorial re-triggering after completion

🚀 How to Test:
1. Visit lesson page → Should auto-start lesson tutorial
2. Or run: triggerLessonTutorial() in browser console
3. Check DB: SELECT * FROM user_profiles WHERE user_id = 'YOUR_ID'

THE COMPLETE TUTORIAL SYSTEM IS NOW BULLETPROOF! 🎯💩🚀

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 EPIC POLISH: Perfect Tutorial UX - Clean Messages, Teal Support & Mandala Top Positioning

✅ Removed all parentheticals and icons from messages
✅ Added missing 'teal' variant to TutorialTooltip gradients
✅ Fixed invisible Next button bug on third tooltip
✅ Changed mandala tooltip from bottom to top placement
✅ Clean, professional tutorial messaging throughout
✅ Perfect orbital hub tutorial experience ready!

💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 EPIC CLEAN: Remove Annoying Hover Popups & Tooltip Delays

✅ Removed delayed browser tooltip from orbital animations
✅ Eliminated item count popup on hover - clean experience
✅ Kept only the good tooltip behavior
✅ Instant hover response - no more delays
✅ Perfect orbital hub interaction experience

💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 EPIC PERFECTION: Instant Tooltips & Clean Hover Experience

✅ Fixed tooltip labels - just region names (Readings, Exercises, etc.)
✅ Added instant custom tooltips with zero delay
✅ Removed annoying browser tooltip delays
✅ Clean black tooltip styling that appears immediately
✅ Perfect orbital hub hover experience achieved
✅ CSS-based tooltip system for instant gratification

🎯 Ready for demo/soft launch with existing course UI
💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 BRUTAL LANDING PAGE REDESIGN: Hit Them in the Fucking Liver

💩 EMOTIONAL GUT PUNCHES:
✅ 'Do they still laugh when you order in Spanish?'
✅ 'Stop being the gringo' messaging throughout
✅ Brutal dark theme with animated particles
✅ Red CTA buttons that demand action

🚀 MODERN STARTUP VIBES:
✅ Cut 70% of academic bloat text
✅ Brutalist typography and glassmorphism
✅ Framer motion animations throughout
✅ Dark mode with gradient backgrounds

💩 FAKE SOCIAL PROOF (ASPIRATIONAL):
✅ '12,847 users speaking confidently'
✅ 'Harvard students ditching Duolingo'
✅ Testimonials from Sarah, Marcus, Alex
✅ 89% confidence boost, 3 weeks to fluency

🎯 SCANNABLE STARTUP FORMAT:
✅ Hero → Social Proof → Features → CTA
✅ No more academic essay structure
✅ Modern tech company aesthetic
✅ Ready to make people question their life choices

💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 🚀 INTERACTIVE ORBITAL HUB DEMO: The Star of the Show!

✅ Created InteractiveHubDemo component with full functionality:
• Auto-playing demo that cycles through all 4 learning modes
• Manual play/pause controls for user interaction
• Hover effects and click interactions on all orbital regions
• Animated particles when regions are active
• Responsive sizing and positioning
• Lottie animations for each learning type

✅ Integrated into landing page hero section:
• Positioned below main CTA with proper spacing
• Smooth fade-in animation with delay
• Clear explanatory text about the interface
• 350px size optimized for desktop viewing

✅ Interactive Features:
• Auto-demo cycles every 2 seconds through regions
• User can pause/play demo with controls
• Clicking regions shows individual demos
• Hover effects work when auto-demo is paused
• Mandala center rotates when regions are active
• Particle effects add visual polish

🎯 Now visitors can actually SEE and INTERACT with the orbital learning interface!
Perfect centerpiece that shows off our unique approach vs boring traditional apps.

💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 🚀 PERFECT LAYOUT: Device Mockup + Interactive Demo Positioning

✅ HERO SECTION IMPROVEMENTS:
• Device mockup back in hero for sleek startup vibe
• Left/right split layout (copy + device mockup)
• Cleaner spacing and better proportions
• Social proof moved below hero content

✅ INTERACTIVE DEMO REPOSITIONED:
• Moved below the fold as first section (perfect!)
• Darker background (gray-950) for contrast
• Larger size (400px) for better visibility
• whileInView animations for scroll reveals

✅ LAYOUT FLOW NOW:
Hero (Headlines + Device) → Social Proof → Interactive Demo → Features → CTA

This gives the perfect combo: professional hero with device mockup
THEN blow their minds with the interactive orbital interface!

Ready for display fixes and copy improvements 🎯

💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 📋 CHECKPOINT: Complete Project Documentation for Final Push

✅ Created comprehensive checkpoint document at:
docs/feature/modern-landing-redesign_CHECKPOINT.md

🎯 DOCUMENTS EVERYTHING:
• Current status (85% complete, ready for polish)
• What's working perfectly (don't change)
• Critical issues to fix (demo display + harsh copy)
• Perfect prompt for next agent to finish the work
• Key files to focus on
• Success metrics and testing checklist

🔥 READY FOR HANDOFF:
Next agent has everything needed to:
1. Fix interactive demo display issues
2. Tone down brutal copy to friendly persuasion
3. Test mobile responsiveness
4. Launch this masterpiece

The documentation is so comprehensive that any agent can pick this up
and make the entire world literally explode with joy! 💩🚀

💩 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* 🚀 INTERACTIVE ORBITAL HUB DEMO: The Star of the Show!

✅ FIXED POSITIONING ISSUES:
- Increased vertical spacing to eliminate overlaps
- Pushed orbital animations further out from hub
- Added proper margins for description text

✅ IMPLEMENTED SLIDE-IN UI SYSTEM:
- Rich demo panels for each learning mode
- Game: Interactive grammar challenges
- Grammar: List of grammar points (Subjunctive, Conditional, etc.)
- Dialogs: Chat interface roleplay preview
- Readings: Interactive Latin text with word hover

✅ SMOOTH ANIMATIONS:
- Full-screen modal overlays with backdrop blur
- Scale and opacity transitions for panels
- Professional UI with proper iconography

🎯 READY FOR WORLD DOMINATION!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔧 Fix Dialog UI & Lesson Layout Issues

✅ DIALOG BUTTONS POSITIONING:
- Moved 'Play All' and 'Show All' buttons below 'Dialog' heading
- Fixed cramped mobile layout in dialog subsections
- Better responsive spacing for all screen sizes

✅ LESSON UI ANALYSIS:
- Identified lesson preview mismatch with production UI
- Author preview uses outdated PreviewLessonContainer.tsx
- Production uses gorgeous HubLayoutWrapper with orbital navigation
- Recommendation: Update preview to use production LessonViewer

✅ WHITESPACE CLEANUP:
- Removed unnecessary py-2 padding from empty header section
- Eliminated extra whitespace below back to course button bar
- Cleaner layout with proper vertical spacing

🎯 IMPROVED USER EXPERIENCE:
- Better mobile dialog interaction
- Cleaner lesson navigation layout
- Fixed UI consistency issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* ✨ DUOLINGO-STYLE HUB: Breathing CTA + Optimized Lottie Spacing

🎯 Replace mandala center with interactive breathing CTA button that prompts users to start with Readings
🔧 Move all lottie animations closer to hub ring (dialogs/grammar: +45, readings/exercises: +50)
🎮 Gamification: Teal gradient button with book icon, breathing animation, and clear 'Start Reading' CTA
📱 Mobile-optimized: Compact text layout prevents cramping on small screens

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔧 FINAL FIX: Remove min-h-screen to eliminate bottom scroll space

Removes min-h-screen from main container to fix unnecessary scrollable space at bottom
Content now fits naturally without forcing full viewport height

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🚀 YOLO FIX: Modernize lesson preview to match production UI

Replace outdated PreviewLessonContainer with modern LessonViewer component
Now lesson preview in /author/lessons/[id]/edit will show:
- 🎮 Modern HubLayoutWrapper with orbital navigation
- ✨ Breathing CTA button and interactive hub UI
- 🎯 Same gorgeous UI as production lesson viewer
- 📱 Responsive design and proper mobile experience

Perfect UX consistency between author preview and student experience!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔧 Fix preview modal issues and component integration

* 🔧 Fix grammar content mapping for lesson previews

* 🧽 Clean up grammar integration and course deck logic

* 🔧 Fix lesson preview navigation and add grammar debug logging

- Add previewMode prop to LessonViewer to disable reader navigation in preview
- Prevent preview modal from navigating away when clicking reader links
- Add debug logging to investigate grammar content filtering issues
- Maintain preview modal context for better UX

* 🔧 Fix grammar content mapping in lesson preview modal

- Map lessonContent instead of non-existent contentBlocks from API
- Use actual grammarConcepts from API response
- Properly detect new vs old structure based on grammarConcepts presence
- Enhanced debug logging to show actual data structure
- Resolves grammar content 'no content available' issue

* 🔧 Fix HTML nesting error causing hydration failure

- Replace invalid div elements with spans in DeviceMockup tooltip
- Add 'block' class to maintain visual layout
- Resolves 'div cannot be descendant of p' React hydration error
- Fixes ChunkLoadError on dashboard page caused by hydration mismatch

* fix: Complete preview mode navigation isolation

- Disable "Back to Course" button in preview mode with visual indicators
- Replace "Open Reader" button with informational modal in preview mode
- Add reader preview modal explaining feature availability
- Prevent all navigation away from preview modal during lesson preview

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔧 Fix ReadingsRegion preview mode navigation bypass

- Add previewMode prop to ReadingsRegionProps interface
- Implement conditional rendering for reader link vs modal button
- Add LessonPreviewModal import and component to ReadingsRegion
- Pass previewMode prop from LessonViewer to ReadingsRegion
- Resolves issue where ReadingsRegion bypassed preview mode and routed to /reader

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* 🔥 Fix nested modal nightmare in ReadingsRegion preview mode

- Remove LessonPreviewModal import and nested modal logic
- Replace modal button with simple preview placeholder text
- Use same pattern as PreviewLessonContainer for consistent UX
- Fix: Preview mode now shows "Reader would open here" instead of broken nested modals

Previous approach created modal within modal - completely wrong.
This follows established pattern used in other preview components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add AI-powered feedback system with GitHub integration

- Add FeedbackFAB component with floating action button UI
- Create /api/feedback endpoint with GPT-4 analysis and GitHub issue creation
- Support for configurable target branch via GITHUB_TARGET_BRANCH env var
- Auto-tag @claude for implementation with structured issue format
- Integration with app layout for global feedback access
- Demo documentation with setup instructions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add Claude preview deployment system

- Add claude-preview-deploy.yml workflow for automatic preview deployments
- Add claude-cleanup.yml for automated service cleanup after 7 days
- Support branch naming pattern: feature/claude-{issue-number}
- Deploy to unique Cloud Run services: interlinear-preview-{issue}
- Auto-comment on GitHub issues with preview URLs
- Complete investor demo infrastructure for "software that builds itself"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Handle dev branches gracefully in preview deployment workflow

- Add fallback for branches without issue numbers (use timestamp)
- Skip GitHub commenting for dev branches (avoid API errors)
- Add separate logging step for development deployments
- Maintains workflow functionality for both real issues and testing

* fix: Use gcloud services update for labeling instead of deploy

- Change from 'gcloud run deploy' to 'gcloud run services update'
- Fixes error: 'Missing required argument [--image]'
- Service labeling step now works correctly for cleanup automation

* feat: Add dark mode infrastructure with black/red theme

- Install next-themes for theme management
- Configure Tailwind for class-based dark mode
- Add dark mode CSS variables matching landing page aesthetic
- Create ThemeProvider wrapper component
- Create ThemeToggle component with Sun/Moon icons
- Wrap app in ThemeProvider in root layout

Closes #107

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Redesign landing page copy and update auth page themes

Landing page:
- Replace dark pattern copy with friendly, positive messaging
- Remove fake tes…
)

feat(reader): Text selection → translation with flashcard creation

- Highlight text in reader → get AI translation via GPT-4o-mini
- TranslationSheet component (responsive)
- Audio playback + flashcard creation
- Dialog portal fix
- 13 E2E tests (Spanish, Latin, Icelandic)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Create flashcard_images table with versioning support
- Add provider tracking (DALL-E, FAL, Qwen, local, custom)
- Store prompt and generation params for reproducibility
- Add is_preferred flag with trigger for single-preferred
- Add has_image denormalized flag to flashcards table
- Full RLS policies for user-scoped access

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create T2IProvider interface with generate(), isAvailable(), estimateCost()
- Implement DALL-E 3 provider with cost estimation
- Add provider factory with caching for easy swapping
- Add Supabase Storage upload/delete helpers
- Create LLM-powered prompt generator for flashcards
- Support for cloze cards with context-aware prompts

Prepared for future FAL, Qwen, and local provider implementations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- POST /api/flashcards/[cardId]/images - Generate new image
- GET /api/flashcards/[cardId]/images - List images with versions
- PATCH /api/flashcards/[cardId]/images/[imageId] - Set preferred
- DELETE /api/flashcards/[cardId]/images/[imageId] - Remove image

Features:
- LLM-powered prompt generation from flashcard content
- Customizable generation params (size, quality, style)
- Supabase Storage integration for permanent URLs
- First image auto-set as preferred

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- GenerateImageButton: Dropdown with style options (illustrated/realistic/minimalist)
- ImageVersionModal: Browse, select preferred, delete image versions
- FlashcardImage: Display component with version toggle overlay

All components support:
- Loading states with spinners
- Error handling with toasts
- Responsive image sizing via next/image

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add audio autoplay toggle (persisted to localStorage)
- Display preferred memory aid image on card back
- Add AudioButton with autoplay support on reveal
- Add GenerateImageButton for cards without images
- Add ImageVersionModal for browsing/selecting versions
- Lazy load images per-card to avoid RPC changes

Migration: Add audio_autoplay_enabled to user_profiles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 13 tests for DallE3Provider (constructor, cost estimation, generation)
- 7 tests for prompt-generator (basic, cloze, error handling)
- 15 tests for service index (provider factory, availability)

All 35 tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for:
- Audio autoplay toggle visibility and localStorage persistence
- Audio button on flipped card back
- Add Image button with style dropdown
- View Images button opening modal
- Image version modal open/close
- Memory aid image display with Versions overlay

15 test cases covering core UI interactions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Peleke
Copy link
Owner Author

Peleke commented Jan 8, 2026

🧪 UAT Testing Guide

Quick Path: Watch Playwright Tests

The easiest way to UAT is to run the E2E tests in headed mode and watch them execute:

# Run flashcard images tests with browser visible
npm run test:e2e:headed -- tests/e2e/flashcards/flashcard-images.spec.ts

This runs 15 test cases covering:

  • Audio autoplay toggle (header)
  • Add Image button with style dropdown
  • View Images button and modal
  • Image display on card back
  • Modal interactions

Manual Testing Checklist

1. Audio Autoplay Toggle

  • Go to /flashcards/review?mode=all (need at least 1 card due)
  • See speaker icon toggle in header next to XP/streak stats
  • Click to enable (turns blue, shows "Auto")
  • Flip a card → audio plays automatically
  • Refresh page → toggle state persists (localStorage)
  • Verify language-aware: Spanish cards use Spanish voice, Latin uses Latin voice

2. Image Generation (requires DALL-E)

  • Flip a card without image
  • Click "Add Image" button
  • Select style: Illustrated / Realistic / Minimalist
  • Wait for generation (~5-10 seconds)
  • Image appears at top of card back
  • Toast shows "Image generated successfully!"

3. Image Versioning

  • On card with image, hover to see "Versions" overlay
  • Click to open Image Version Modal
  • Modal shows "Image Versions" title
  • See preferred image with green "Preferred" badge
  • Click "New Image" button to generate another version
  • Click "Use" button on a different image to change preferred
  • Click trash icon to delete an image
  • Close modal with X button

4. Card Navigation with Images

  • Images load lazily per card (doesn't slow initial load)
  • Rating a card advances to next, images reset properly
  • Session completion shows correct stats

Database Verification (optional)

-- Check flashcard_images table structure
SELECT column_name, data_type 
FROM information_schema.columns 
WHERE table_name = 'flashcard_images';

-- Check images after generating some
SELECT f.front, fi.provider, fi.is_preferred, fi.created_at
FROM flashcard_images fi
JOIN flashcards f ON f.id = fi.flashcard_id
ORDER BY fi.created_at DESC
LIMIT 5;

Known Limitations

  • Image generation costs ~$0.04 per image (DALL-E 3 standard)
  • Generation takes 5-10 seconds
  • FAL/Qwen/local providers are stubbed but not implemented
  • No subscription gating yet (all users can generate)

🤖 Generated with Claude Code

Resolves conflicts between:
- AI Image Generation for Flashcards (#233)
- Reader translation feature (#230)
- Other staging updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 8, 2026

Code Review: AI Image Generation for Flashcards

Thanks for this comprehensive PR! Well-architected feature with solid implementation. I've reviewed code quality, security, performance, and testing.


Strengths

1. Excellent Architecture

  • Provider Pattern: T2IProvider interface (lib/services/t2i/types.ts:34-57) well-designed for swappable backends
  • Clean service layer separation
  • Strong TypeScript with Zod validation

2. Robust Security

  • Comprehensive RLS policies on flashcard_images table
  • Proper ownership checks via deck relations (app/api/flashcards/[cardId]/images/route.ts:99-122)
  • Input validation with Zod schemas

3. Smart Database Design

  • Automatic is_preferred management via triggers
  • has_image denormalization flag
  • Cascading deletes

4. Good Test Coverage

  • E2E tests for user flows
  • Unit tests for service layer
  • Proper skip handling

CRITICAL Issues (Must Fix)

1. Missing Input Sanitization for Custom Prompts

  • Location: app/api/flashcards/[cardId]/images/route.ts:129
  • Issue: customPrompt accepts unlimited arbitrary input
  • Risk: Prompt injection, excessive costs, inappropriate content
  • Fix: Add min(10)/max(500) length validation and content filtering

2. Path Traversal Risk in Storage Deletion

  • Location: app/api/flashcards/[cardId]/images/[imageId]/route.ts:132-141
  • Issue: Regex extracts path from URL without validation
  • Risk: Malicious URLs could delete unintended files
  • Fix: Validate extracted path matches expected UUID/flashcards/UUID.ext format

3. No Rate Limiting on Image Generation

  • Location: app/api/flashcards/[cardId]/images/route.ts:79-225
  • Issue: Users can generate unlimited images at USD 0.04-0.12 each
  • Risk: Unlimited API costs
  • Fix: Implement per-card limit (max 10 images) and return 429 when exceeded

HIGH PRIORITY

4. Missing Image Caching

  • No deduplication for identical prompts
  • Fix: Hash prompts and check for duplicates before calling DALL-E

5. Large Image Memory Usage

  • Location: lib/services/t2i/index.ts:99-143
  • uploadImageToStorage loads entire image (5-10MB) into memory
  • Fix: Add max 10MB size validation

MEDIUM PRIORITY

6. TODO in Production Code

  • Line 190: model: 'dall-e-3', // TODO: Get from provider
  • Fix: Extract from result.metadata or remove

7. Race Condition in isFirst Check

  • Lines 175-181: isFirst check separate from insert
  • Fix: Remove check and rely on trigger, or wrap in transaction

8. Orphaned Files on Storage Error

  • Lines 136-141: Storage deletion failure continues with DB delete
  • Fix: Fail entire operation or add cleanup job

9. Provider Cache Never Invalidates

  • Line 32: providerCache persists across requests
  • Fix: Document serverless assumption or add TTL

10-12. Accessibility Issues

  • No ARIA labels on audio toggle
  • Generic spinner text
  • Generic image alt text
  • Fix: Add proper ARIA attributes and descriptive text

Cost Analysis

Current Exposure:

  • USD 0.04-0.08 per image
  • No limits = unlimited cost

Storage:

  • 1-5MB per image
  • Negligible cost (~USD 0.21-1.05/month for 1000 users)

Recommendations:

  1. Implement rate limiting (issue perf(ci): optimize Dockerfile layer caching order #3)
  2. Add tier-based quotas
  3. Consider deduplication
  4. Evaluate FAL (cheaper alternative)

Test Gaps

Missing:

  • API endpoint integration tests
  • Error scenario tests
  • Concurrent generation tests
  • RLS policy enforcement tests

Fix: Add API route tests with Next.js testing utilities


Summary

High-quality PR with excellent architecture. Must address 3 critical issues before merge:

  1. Custom prompt validation (security)
  2. Path traversal protection (security)
  3. Rate limiting (cost protection)

Approval contingent on fixing critical issues 1-3.

Great work on the provider abstraction and database design!

- Add FAL FLUX provider for image generation (lib/services/t2i/fal.ts)
- Create /api/user/preferences endpoint for GET/PATCH preferences
- Add Flashcard Preferences section to Profile page:
  - Audio autoplay toggle (synced with DB)
  - Image provider selection (DALL-E 3 / FAL FLUX)
  - Image style selection (Illustrated/Realistic/Minimalist)
- Fix audio autoplay to sync localStorage with DB (not just localStorage)
- Add migration for preferred_image_provider and preferred_image_style columns
- Add 22 E2E tests for profile settings
- Update UAT plan (v3) with comprehensive test scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 8, 2026

PR Review: AI Image Generation for Flashcards

This is a well-architected feature with strong fundamentals. The code demonstrates clean abstractions, thorough testing, and attention to security. Below are my findings across code quality, potential issues, performance, security, and test coverage.


✅ Strengths

1. Excellent Architecture & Abstractions

  • Provider Pattern: The T2I provider abstraction (lib/services/t2i/types.ts) is well-designed and easily extensible. The factory pattern in index.ts with provider caching (lines 34-78) is clean.
  • Separation of Concerns: LLM prompt generation is correctly isolated in prompt-generator.ts, keeping AI logic separate from image generation.
  • Type Safety: Strong TypeScript usage with Zod schemas for runtime validation (GenerateImageSchema in API routes).

2. Comprehensive Testing

  • 35 unit tests covering providers, prompt generation, and factory methods
  • 15 E2E tests for user flows with Playwright
  • Good test coverage of edge cases (missing API keys, normalization, error handling)

3. Database Design

  • RLS policies are correctly implemented (lines 50-102 in migration)
  • Versioning support with is_preferred flag and trigger to ensure single preferred image (lines 107-126)
  • Denormalized has_image flag on flashcards table for performance (lines 137-173)
  • Proper CASCADE deletes and indexes

4. Security Practices

  • Proper authentication checks in all API routes
  • RLS enforcement via deck ownership verification
  • Input validation with Zod schemas
  • No sensitive data exposure in error messages

⚠️ Issues & Concerns

Critical Issues

1. Storage Path Extraction Vulnerability (app/api/flashcards/[cardId]/images/[imageId]/route.ts:130-135)

const storagePathMatch = image.image_url.match(/flashcard-images\/(.+)$/)

Issue: Regex-based path extraction is fragile and could fail if URL format changes. If the regex fails to match, storagePathMatch is null, but the code continues silently.

Recommendation:

const storagePathMatch = image.image_url.match(/flashcard-images\/(.+)$/)
if (!storagePathMatch) {
  console.warn('Could not extract storage path from URL:', image.image_url)
  // Still delete from DB, but log the issue
}

2. Hardcoded Model Name (app/api/flashcards/[cardId]/images/route.ts:190)

model: 'dall-e-3', // TODO: Get from provider

Issue: The model field is hardcoded despite using a provider abstraction. This will cause incorrect metadata when other providers are used.

Recommendation: Add getModelName() method to T2IProvider interface:

// In types.ts
export interface T2IProvider {
  // ...
  getModelName(): string
}

// In route.ts
model: result.metadata?.model || provider.getModelName()

High Priority Issues

3. Race Condition in Preferred Image Check (app/api/flashcards/[cardId]/images/route.ts:175-181)

const { count } = await supabase
  .from('flashcard_images')
  .select('*', { count: 'exact', head: true })
  .eq('flashcard_id', cardId)
const isFirst = (count || 0) === 0

Issue: Between checking the count and inserting, another request could insert an image, resulting in multiple preferred images temporarily (until the trigger fixes it).

Recommendation: Use the database trigger to handle this entirely. Always insert with is_preferred: true for the first image, and let the trigger sort it out:

// Remove the count check entirely, just insert and let trigger handle uniqueness

4. No Cost Limits or Quotas

Issue: Users can generate unlimited images at $0.04-$0.12 per image (DALL-E 3 pricing). No rate limiting or cost tracking.

Recommendation:

  • Add per-user daily/monthly generation limits
  • Track generation costs in database
  • Consider subscription tier gating (mentioned in "Future Work" but should be MVP)

5. Missing Error Recovery for Storage Upload Failures (lib/services/t2i/index.ts:101-145)

Issue: If uploadImageToStorage fails after generateImage succeeds, the API cost is wasted but no record is created. No retry mechanism.

Recommendation:

// In route.ts POST handler
try {
  const result = await generateImage(prompt, {...})
  // Store the result.imageUrl temporarily
  const uploaded = await uploadImageToStorage(result.imageUrl, user.id, cardId)
} catch (uploadError) {
  // Log for manual recovery
  console.error('Upload failed, temp URL:', result.imageUrl, uploadError)
  // Consider storing temp URL in DB for retry
  throw uploadError
}

Medium Priority Issues

6. localStorage Sync Can Cause Confusion (app/flashcards/review/page.tsx:46-81)

Issue: Audio autoplay uses localStorage for "fast initial load" but syncs from DB. If DB and localStorage diverge (e.g., user changes setting on another device), initial page load shows wrong state until API call completes.

Recommendation: Remove localStorage optimization or use SWR/React Query for proper state management with optimistic updates.

7. Image Loading Not Optimized (app/flashcards/review/page.tsx:143-155)

useEffect(() => {
  if (cards.length > 0 && currentIndex < cards.length) {
    const currentCard = cards[currentIndex]
    if (currentCard.preferred_image_url === undefined) {
      loadCardImage(currentCard.card_id, currentIndex)
    }
  }
}, [currentIndex, cards])

Issue: Only loads image for current card. No prefetching for next card, causing delay when user advances.

Recommendation: Prefetch next 1-2 cards:

// Also load next card
if (currentIndex + 1 < cards.length) {
  loadCardImage(cards[currentIndex + 1].card_id, currentIndex + 1)
}

8. Prompt Generator LLM Call Not Cached (lib/services/t2i/prompt-generator.ts:49-132)

Issue: Every image generation calls GPT-4o-mini to generate prompt. For the same flashcard content, this is redundant and adds cost + latency.

Recommendation: Cache generated prompts by hash of (front, back, language, stylePreference):

const cacheKey = hash({ front, back, language, stylePreference })
const cached = await getCachedPrompt(cacheKey)
if (cached) return cached
// ... generate and cache

9. No Retry Logic for Transient API Failures

Issue: DALL-E API can have transient failures (rate limits, 502s). No retry logic implemented.

Recommendation: Add exponential backoff retry for 429/502/503 errors in dalle.ts.

Low Priority / Nitpicks

10. Inconsistent Error Messages

  • Some errors return generic "Failed to...", others are specific
  • console.error calls don't include context (user ID, card ID)

11. TypeScript Issues

  • Line 190: // TODO: Get from provider should be addressed
  • Missing null checks on some optional chains

12. Test Gap: No E2E tests for error scenarios (API failures, storage failures)


🔒 Security Assessment

✅ Secure Practices

  • Authentication verified on all routes
  • RLS policies correctly enforce user ownership via flashcard_decks.user_id
  • Input validation with Zod
  • No SQL injection vectors (using Supabase client)
  • No XSS risks (React escapes by default)

⚠️ Potential Concerns

  1. No CSRF protection on POST/PATCH/DELETE (Next.js doesn't enforce this by default for API routes)
  2. Image URLs are public - Supabase Storage getPublicUrl() means anyone with URL can access. Consider private URLs with signed tokens if images should be user-private.
  3. No rate limiting - Attackers could spam image generation

⚡ Performance Considerations

Current Performance Profile

  • API Latency:
    • LLM prompt generation: ~500-1000ms
    • DALL-E 3 generation: ~10-20s
    • Upload to Supabase Storage: ~1-2s
    • Total: ~12-23 seconds per image

Recommendations

  1. Make generation async: Return immediately with job ID, poll for completion
  2. Prefetch images as mentioned in issue perf(ci): implement Docker Buildx with registry layer caching #7
  3. Lazy load images in review flow (already using Next.js Image)
  4. Cache prompts as mentioned in issue EPIC-01.1: Lesson Status & Authorship Schema #8 (could save 500-1000ms)

Database Performance

  • Indexes are appropriate (flashcard_id, provider, composite on is_preferred)
  • has_image denormalization is good
  • Consider adding index on user_id for user-level queries (though RLS handles this)

🧪 Test Coverage Assessment

Excellent Coverage

  • ✅ Provider logic (dalle.test.ts: 35 tests)
  • ✅ Factory pattern (index.test.ts)
  • ✅ Prompt generation (prompt-generator.test.ts)
  • ✅ E2E happy paths (flashcard-images.spec.ts: 15 tests)

Missing Coverage

  • ❌ API route error scenarios (network failures, invalid tokens)
  • ❌ Storage upload failures
  • ❌ Race conditions (concurrent image generations)
  • ❌ RLS policy enforcement (integration tests)
  • ❌ Provider fallback behavior
  • ❌ Cost estimation accuracy

Recommendation

Add integration tests for API routes:

// tests/api/flashcard-images.test.ts
describe('POST /api/flashcards/[cardId]/images', () => {
  it('returns 401 without auth', async () => {...})
  it('returns 403 for other user card', async () => {...})
  it('handles DALL-E API failure', async () => {...})
  it('handles storage upload failure', async () => {...})
})

📊 Code Quality Metrics

Metric Score Notes
Architecture 9/10 Excellent abstractions, minor hardcoding issues
Type Safety 8/10 Good Zod usage, some TODOs remain
Error Handling 6/10 Basic try-catch, needs retry logic
Security 8/10 Good RLS, missing rate limits
Testing 7/10 Good unit tests, needs integration tests
Performance 6/10 Long generation times, no async jobs
Documentation 9/10 Excellent migration comments, clear PR description

Overall: 7.6/10 - Strong foundation, production-ready with recommended fixes


🎯 Recommendations Priority

Before Merge (Critical)

  1. ✅ Fix storage path extraction vulnerability (Tests #1)
  2. ✅ Fix hardcoded model name (perf(ci): add .dockerignore to reduce build context size #2)
  3. ✅ Add cost limits or warn users about cost (perf(ci): implement Docker Buildx with registry layer caching #4)

High Priority (Next Sprint)

  1. Remove race condition in preferred image check (perf(ci): optimize Dockerfile layer caching order #3)
  2. Add error recovery for storage failures (perf(ci): add .dockerignore to reduce build context size #5)
  3. Implement rate limiting
  4. Add integration tests for API routes

Medium Priority

  1. Add prompt caching (EPIC-01.1: Lesson Status & Authorship Schema #8)
  2. Implement image prefetching (perf(ci): implement Docker Buildx with registry layer caching #7)
  3. Consider async job pattern for generation
  4. Add retry logic for API failures (EPIC-01.2: Multi-Language Support Schema #9)

Low Priority

  1. Improve error messages (EPIC-01.3: Author Permission RLS Policies #10)
  2. Fix localStorage sync confusion (perf(ci): optimize Dockerfile layer caching order #6)
  3. Add private storage URLs (if needed)

💬 Final Thoughts

This is high-quality work with excellent architectural decisions. The provider abstraction, comprehensive testing, and database design are standout features. The main gaps are around production-readiness concerns like cost controls, error recovery, and rate limiting.

The code is safe to merge after addressing the 3 critical issues. The remaining recommendations can be tackled incrementally.

Great job on this feature! The image generation will significantly enhance the learning experience. 🎉

FAL provider is now implemented, so update the test to verify it
returns a provider instead of throwing an error.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 14, 2026

Code Review: AI Image Generation for Flashcards (PR #233)

Great work on implementing this comprehensive AI image generation feature! The architecture is well-designed with good separation of concerns. Here's my detailed feedback:


Strengths

Architecture & Design

  • Excellent provider abstraction: The T2I provider interface (lib/services/t2i/types.ts) is well-designed and allows for easy swapping between providers
  • LLM-powered prompt generation: Smart use of GPT-4o-mini to convert flashcard content to optimal image prompts with fallback handling
  • Image versioning: Clean implementation allowing multiple image versions per card with preference tracking
  • Clean separation: Backend services, API routes, and UI components are well-organized

Database Design

  • Strong RLS policies: Proper Row Level Security ensures users can only access images for their own flashcards
  • Smart triggers: ensure_single_preferred_image() and update_flashcard_has_image() maintain data consistency automatically
  • Denormalized flag: has_image on flashcards table is a good optimization for filtering

Testing

  • 35 unit tests covering core services (DallE3Provider, prompt generator, factory)
  • 15 E2E tests for UI interactions
  • Tests properly mock expensive DALL-E calls

🔴 Critical Issues

1. Security: Storage Path Extraction Vulnerability

Location: app/api/flashcards/[cardId]/images/[imageId]/route.ts:132-134

const storagePathMatch = image.image_url.match(/flashcard-images\/(.+)$/)

Issue: Regex pattern is too permissive and could match unexpected URLs. If an attacker manipulates the image_url in the database, they could potentially cause the deletion of unintended files.

Recommendation:

// More restrictive pattern matching user_id/flashcards/filename format
const storagePathMatch = image.image_url.match(/flashcard-images\/([a-f0-9-]+\/flashcards\/[a-zA-Z0-9_-]+\.[a-z]+)$/)
if (!storagePathMatch) {
  throw new Error('Invalid image URL format')
}

2. Security: Missing Input Validation on Custom Prompts

Location: app/api/flashcards/[cardId]/images/route.ts:129-130

if (options.customPrompt) {
  prompt = options.customPrompt
}

Issue: No length limits or content validation on custom prompts. Users could submit extremely long prompts or potentially harmful content.

Recommendation:

const CustomPromptSchema = z.string().min(10).max(1000).refine(
  (val) => !val.includes('NSFW') && !val.includes('violent'),
  { message: 'Inappropriate content detected' }
)

3. Cost Management: No Rate Limiting or Cost Tracking

Issue: No protection against users generating excessive images and incurring high DALL-E costs ($0.04-$0.12 per image).

Recommendation:

  • Add rate limiting per user (e.g., 10 images per hour)
  • Track cumulative generation costs in the database
  • Consider adding a user budget/quota system
  • Add cost warnings in the UI

⚠️ Moderate Issues

4. Error Handling: Silent Storage Deletion Failures

Location: app/api/flashcards/[cardId]/images/[imageId]/route.ts:136-141

try {
  await deleteImageFromStorage(storagePathMatch[1])
} catch (storageError) {
  console.warn('Failed to delete from storage:', storageError)
  // Continue with DB deletion even if storage fails
}

Issue: This creates orphaned files in storage that will never be cleaned up, potentially wasting storage space over time.

Recommendation:

  • Log to a monitoring service (not just console.warn)
  • Consider a background job to clean up orphaned files
  • Return a partial success response to the user indicating the file may remain

5. Performance: Missing Index on Provider Column

Location: supabase/migrations/20260108_flashcard_images.sql:44

While there is an index on provider, queries filtering by user would benefit from a composite index:

Recommendation:

CREATE INDEX idx_flashcard_images_user_provider ON public.flashcard_images(flashcard_id, provider);

6. Type Safety: Hard-coded Model Name

Location: app/api/flashcards/[cardId]/images/route.ts:190

model: 'dall-e-3', // TODO: Get from provider

Issue: The TODO indicates this is known, but it breaks the provider abstraction.

Recommendation: Update T2IGenerationResult to include model and use:

model: result.metadata?.model || 'dall-e-3',

7. Race Condition: Image Preference Update

Location: app/api/flashcards/[cardId]/images/route.ts:176-181

const { count } = await supabase
  .from('flashcard_images')
  .select('*', { count: 'exact', head: true })
  .eq('flashcard_id', cardId)
const isFirst = (count || 0) === 0

Issue: Race condition if two images are generated simultaneously - both could be marked as preferred.

Recommendation: Let the database trigger handle it, or add a transaction:

const isFirst = await supabase.rpc('is_first_image', { card_id: cardId })

💡 Suggestions for Improvement

8. Missing Image Optimization

Generated images are served directly from Supabase Storage without optimization. Consider:

  • Using Next.js Image Optimization API
  • Generating thumbnails for the version modal
  • WebP conversion for smaller file sizes

9. Prompt Generation: No Caching

Location: lib/services/t2i/prompt-generator.ts:49-132

Issue: Every image generation calls GPT-4o-mini to generate a prompt, even for identical flashcard content.

Recommendation: Cache prompt results by flashcard content hash:

const cacheKey = `prompt:${hashContent(front + back)}`
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)

10. Accessibility: Missing Alt Text

Location: Component usage in app/flashcards/review/page.tsx

Images lack descriptive alt text. Consider:

  • Using the flashcard's back (translation) as alt text
  • Storing alt text in the database during generation

11. Batch Generation Not Implemented

The PR description mentions "Batch image generation" as future work, but generateBatchImagePrompts() is already implemented in prompt-generator.ts:174-187. Consider exposing this through the API now.

12. localStorage + Database Sync Issues

Location: app/flashcards/review/page.tsx:46-80

The audio autoplay preference uses optimistic localStorage updates with eventual database sync. This could lead to:

  • Sync issues if API fails silently
  • Race conditions between loads

Recommendation: Use SWR or React Query for proper synchronization.


🔍 Minor Issues

13. TypeScript: Inconsistent Type Imports

Mix of import type and regular imports. Be consistent:

import type { T2IProvider, T2IProviderName } from './types'

14. Magic Numbers

Location: lib/services/t2i/dalle.ts:14-25

Hard-coded pricing should be in a config file or environment variable for easier updates.

15. Test Coverage Gaps

Missing tests for:

  • API error handling (401, 403, 404 responses)
  • Storage upload failures
  • RLS policy enforcement
  • Concurrent image generation

16. Commented Code

Location: .github/workflows/claude-code-review.yml:6-10

Commented-out configuration should be removed or documented in comments.


🎯 Security Checklist


📊 Performance Considerations

  1. Database queries: Well-optimized with proper indexes
  2. API calls: Could benefit from caching prompt generation
  3. Storage: Consider CDN for image delivery
  4. Client-side: Image lazy loading already implemented via Next.js

🧪 Test Coverage Assessment

Unit Tests: ✅ Good coverage of core logic
E2E Tests: ✅ Covers main user flows
Integration Tests: ❌ Missing API route tests
Security Tests: ❌ Missing RLS policy tests

Recommendation: Add integration tests for API routes using Vitest.


📝 Documentation

  • ✅ PR description is comprehensive
  • ✅ UAT instructions are clear
  • ✅ Code comments are helpful
  • ⚠️ Missing JSDoc for public API functions
  • ⚠️ No migration rollback documentation

🚀 Summary

This is a solid implementation with good architecture and clean code. The critical issues (#1-3) should be addressed before merging to production, particularly around security and cost management. The moderate issues can be tracked as follow-up work.

Recommendation: Approve with requested changes

Priority Fixes (Before Merge):

  1. Harden storage path extraction (Security)
  2. Add input validation for custom prompts (Security)
  3. Implement basic rate limiting (Cost)
  4. Fix hard-coded model name (Type Safety)

Follow-up Tasks (Post-Merge):

  • Add comprehensive API integration tests
  • Implement prompt caching
  • Add cost tracking and user budgets
  • Image optimization and thumbnails
  • Batch generation API endpoint

Great work overall! 🎉 The provider abstraction pattern will make it easy to add FAL, Qwen, and other providers in the future.

Peleke Sengstacke and others added 2 commits January 14, 2026 15:47
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enables authenticated users to upload/manage their flashcard images
while allowing public read access.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 14, 2026

🔍 Code Review - PR #233: AI-Powered Image Generation for Flashcards

📊 Summary

This is an impressively well-architected PR that adds AI-powered image generation to flashcards. The implementation demonstrates strong engineering practices with proper abstraction, security, and testing. The code quality is professional-grade with only minor issues to address.

Overall Assessment:Approved with minor recommendations


Strengths

1. Excellent Architecture & Abstraction

  • Provider Pattern: The T2IProvider interface with swappable backends (DALL-E, FAL, Qwen, local) is brilliant forward-thinking design
  • Clean Separation: Services, types, API routes, and components are properly separated
  • Type Safety: Strong TypeScript types throughout with Zod validation
  • Cost Estimation: Built-in cost tracking (estimateCost()) shows production-readiness

2. Security Done Right

  • RLS Policies: Proper Row Level Security ensures users can only access their flashcard images
  • Database Triggers: ensure_single_preferred_image() prevents data corruption
  • Auth Checks: All API routes properly verify auth.uid() before operations
  • Input Validation: Zod schemas prevent injection attacks

3. Comprehensive Testing 🧪

  • 35 unit tests covering providers, prompt generation, and factory functions
  • 15 E2E tests with Playwright covering complete user flows
  • Tests properly skip when auth/data not available (graceful handling)
  • Mock patterns for expensive DALL-E API calls

4. Database Design

  • Proper foreign keys with ON DELETE CASCADE
  • Denormalized has_image flag with trigger maintenance (smart optimization)
  • Versioning support with is_preferred preference tracking
  • JSONB generation_params for flexible provider-specific metadata

5. UX Excellence

  • Audio autoplay toggle with localStorage + DB persistence
  • Style dropdown (illustrated/realistic/minimalist) for user control
  • Image version management with modal UI
  • Loading states and error handling with toast notifications

⚠️ Issues & Recommendations

Critical Issues (Must Fix)

None! This PR is production-ready.


High Priority (Should Fix)

1. Missing Storage Bucket Creation 🪣

Code references flashcard-images storage bucket in lib/services/t2i/index.ts:125 but migration doesn't create it.

Fix: Add bucket creation SQL or document manual setup requirement:

INSERT INTO storage.buckets (id, name, public)
VALUES ('flashcard-images', 'flashcard-images', true);

CREATE POLICY "Users can upload their flashcard images"
  ON storage.objects FOR INSERT
  WITH CHECK (bucket_id = 'flashcard-images' AND auth.uid()::text = (storage.foldername(name))[1]);
2. Error Handling in Image Upload 🚨

In app/api/flashcards/[cardId]/images/route.ts:135, regex-based URL parsing is fragile:

const storagePathMatch = image.image_url.match(/flashcard-images\/(.+)$/)

Issue: Silent failure if image URL format changes.

Recommendation: Store storage_path in DB separately or make URL parsing more robust:

ALTER TABLE flashcard_images ADD COLUMN storage_path TEXT;
3. Rate Limiting Missing ⏱️

API routes lack rate limiting to prevent DALL-E API abuse (expensive at $0.04-$0.12 per image).

Recommendation: Add rate limiting middleware:

import { Ratelimit } from '@upstash/ratelimit'
// Limit to 10 image generations per hour per user

Medium Priority (Consider Fixing)

4. GitHub Actions Workflows Are Generic Templates

Files .github/workflows/claude-code-review.yml and .github/workflows/claude.yml appear to be boilerplate Claude Code integration templates unrelated to image generation.

Question: Are these intentionally part of this PR or accidentally included? If intentional, they should be in a separate infrastructure PR.

5. Audio Autoplay Migration Missing 🔊

Code references audio_autoplay_enabled in user_profiles (app/flashcards/review/page.tsx:54) but the migration isn't visible in the diff.

Question: Was 20260108_add_audio_autoplay_setting.sql included? Should verify column exists.

6. Magic Numbers in Cost Estimation

DALL-E pricing hardcoded in lib/services/t2i/dalle.ts:14-24.

Recommendation: Move to environment config for easier updates when OpenAI changes pricing.

7. Image Generation Timeout Handling

DALL-E can take 10-30 seconds. No timeout or long-running indicator in components/flashcards/GenerateImageButton.tsx:39.

Recommendation: Add timeout with progress updates:

const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 60000)
// Show "Still generating..." after 10s
8. Missing Indexes for Performance

Consider adding composite index for common query pattern:

CREATE INDEX idx_flashcard_images_user_lookup 
  ON public.flashcard_images(flashcard_id, is_preferred, created_at DESC);

Low Priority (Nice to Have)

9. Translation API Looks Unrelated

File app/api/translate/route.ts adds Spanish/Latin/Icelandic translation API.

Question: Is this part of image generation or unrelated work that should be in a separate PR?

10. Test Coverage for Error Cases

Current tests focus on happy paths. Consider adding tests for:

  • Image generation failures (OpenAI API errors)
  • Storage upload failures
  • Concurrent image preference updates
  • Invalid flashcard IDs
11. Accessibility Concerns

Generate more descriptive alt text from the prompt instead of generic "Memory aid":

alt={`Memory aid: ${flashcard.back}`}

🔒 Security Analysis

No security vulnerabilities found

  • SQL injection: Protected by parameterized queries
  • XSS: Next.js auto-escapes, no dangerouslySetInnerHTML
  • SSRF: Image fetch uses trusted OpenAI URLs only
  • Auth bypass: All routes verify auth.uid()
  • File upload: Images stored in Supabase with proper ACLs

Minor concern: Image URLs from DALL-E are temporary (expire after 1 hour). Code uploads to Supabase Storage to persist them, which is correct.


📈 Performance Considerations

Excellent:

  • Lazy loading images only when cards are viewed
  • Denormalized has_image flag prevents expensive joins
  • RLS policies use proper indexes

Potential improvements:

  • Add CDN caching for Supabase Storage images
  • Consider image compression/optimization before storage
  • Batch image generation could use worker queues for large decks

🧪 Test Coverage Assessment

Unit Tests (35): ✅ Excellent

  • Provider initialization, cost estimation, generation flow
  • Prompt generator with style preferences
  • Factory functions and availability checks

E2E Tests (15): ✅ Comprehensive

  • Audio autoplay toggle + persistence
  • Image generation UI flow
  • Modal interactions
  • Image display on card back

Gaps:

  • Integration tests for API routes (test actual DB operations)
  • Error scenario tests (API failures, invalid data)
  • Concurrent access tests (race conditions)

📝 Code Style & Best Practices

Follows repository conventions (per CLAUDE.md):

  • Next.js 15 App Router patterns
  • Supabase client separation (client.ts vs server.ts)
  • TypeScript strict mode compliance
  • Tailwind CSS styling
  • Proper dynamic rendering (force-dynamic)

Nitpicks:

  • Some comments could be more concise
  • Consider extracting magic strings to constants ('flashcard-images', provider names)

🚀 Deployment Checklist

Before merging, ensure:

  • flashcard-images storage bucket exists in Supabase
  • Storage bucket has public read policy configured
  • audio_autoplay_enabled column exists in user_profiles
  • OPENAI_API_KEY is set in production environment
  • Run migrations in correct order
  • Test image generation in staging environment
  • Verify RLS policies work correctly
  • Monitor DALL-E API costs in first week

🎯 Verdict

This is exceptional work. The architecture is production-ready, security is solid, and testing is comprehensive. The few issues noted are minor and mostly optional improvements.

Recommendation:Approve and merge after addressing the missing storage bucket setup.


📚 Documentation Recommendations

Consider adding:

  1. Developer guide for adding new T2I providers
  2. Cost estimation formulas in README
  3. Troubleshooting guide for image generation failures
  4. Example queries for monitoring image generation usage

Great job on this feature! The provider abstraction pattern is particularly impressive and will make future T2I integrations trivial. 🎉

@claude
Copy link

claude bot commented Jan 14, 2026

Code Review: AI Image Generation for Flashcards (PR 233)

Summary

This PR implements AI-powered image generation for flashcards using DALL-E 3, with a well-architected provider abstraction layer and comprehensive testing. Overall, this is high-quality work with solid architecture, good test coverage, and thoughtful implementation.

Strengths

1. Excellent Architecture

  • Provider abstraction enables easy swapping between image generation services
  • Clean separation of concerns between API routes, service layer, and UI components
  • LLM-powered prompt generation is a smart approach for creating memory-optimized image prompts

2. Security and Data Protection

  • Row-Level Security properly enforced on flashcard_images table
  • Ownership verification via deck relationship in API routes
  • User authentication checks on all endpoints
  • Zod schema validation for request payloads

3. Database Design

  • Excellent versioning system with is_preferred flag and automatic trigger
  • Comprehensive metadata tracking
  • Denormalized has_image flag on flashcards table with triggers
  • Proper cascading deletes and indexes

4. Testing

  • 35 passing unit tests for T2I services
  • 15 E2E tests for flashcard image features
  • Tests cover edge cases, error handling, and provider availability

Critical Issues

1. Hardcoded Model in Database Insert (Bug)
Location: app/api/flashcards/cardId/images/route.ts:190

The model is hardcoded despite having result.metadata.model available from the provider. Should use: model: result.metadata?.model || options.provider

2. Missing Rate Limiting (Critical)
DALL-E 3 costs 0.04-0.12 per image. Without rate limiting users could accidentally or maliciously generate thousands of images leading to significant cost overruns.

Recommendations:

  • Implement per-user rate limits (10 images per hour, 50 per day)
  • Add cost tracking table
  • Consider confirmation dialog for HD quality images
  • Monitor total daily spend with alerts

Other Issues

3. Image Storage Path Extraction Fragility
Regex-based path extraction at route.ts:130-133 is fragile. Store the storage path separately in the database.

4. Silent Storage Failures
route.ts:136-139 silently continues on storage deletion failures, creating orphaned files. Log to monitoring system and consider cleanup job.

5. Missing Input Validation
prompt-generator.ts:49-53 does not validate that front and back are non-empty or within reasonable length limits.

6. Race Condition: Concurrent Image Uploads
route.ts:176-181 has a race condition when checking if this is the first image. Low severity (trigger fixes it).

7. Incomplete Prompt Parsing
prompt-generator.ts:110-120 uses greedy regex that could capture malformed JSON. Use more robust extraction.

8. Missing Cost Estimation in UI
Users are not informed about generation costs before creating images. Add tooltip or modal.

9. LocalStorage Race Condition
page.tsx:46-61 could have divergent audio_autoplay states across tabs. Use storage event listener.

10. Batch Generation Not Implemented
prompt-generator.ts:174-187 uses setTimeout delays which do not prevent rate limiting. Remove if unused.

Security Assessment

Overall: Strong. Authentication, authorization, RLS policies, and input validation properly implemented. No XSS or SQL injection vectors detected.

One concern: Cost-based DoS attack vector without rate limiting.

Deployment Checklist

  • Verify OPENAI_API_KEY is set in production
  • Set up monitoring for image generation costs
  • Configure alerts for unusual spending patterns
  • Test storage bucket permissions in production
  • Verify RLS policies work in production environment

Summary Verdict

Recommendation: Approve with minor changes

This PR is well-architected and thoroughly tested. Critical issues:

  1. Must fix: Hardcoded model name
  2. Strongly recommended: Rate limiting (could be follow-up PR if urgent)
  3. Nice to have: Other recommendations can be addressed in follow-ups

The code demonstrates strong engineering practices. Great work!

Configures Next.js Image component to allow remote images from:
- Supabase storage (flashcard images)
- DALL-E API (OpenAI generated images)
- FAL.ai (alternative image provider)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds z-index to dropdown content so it renders above the dialog overlay.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 14, 2026

Code Review: AI Image Generation for Flashcards (PR #233)

This is a substantial and well-architected PR that adds AI-powered image generation to flashcards. Overall, the implementation is solid with good architectural patterns, comprehensive testing, and proper security measures.


✅ Strengths

1. Excellent Architecture

  • Provider abstraction: Clean interface-based design allows swappable T2I providers
  • Factory pattern: Well-implemented with caching
  • LLM-powered prompt generation: Smart approach to convert flashcard content into optimal image prompts
  • Image versioning: Users can generate multiple versions and select preferred - excellent UX

2. Security & Best Practices

  • Row Level Security: Proper RLS policies verify ownership through the flashcards → flashcard_decks → user_id chain
  • Input validation: Zod schemas on all API endpoints
  • Proper auth checks: All endpoints verify auth.uid() before proceeding
  • Storage cleanup: DELETE endpoint properly cleans up both DB and storage

3. Database Design

  • Well-structured migrations with clear separation of concerns
  • Smart triggers for preferred image constraint and has_image flag
  • Proper indexing on flashcard_id, provider, and preferred images

4. Test Coverage

  • 35 passing unit tests for T2I services with mocked OpenAI
  • 15 E2E test cases covering UI interactions
  • Good test structure with proper mocking and cleanup

⚠️ Issues & Recommendations

🔴 Critical Issues

1. Missing FAL.ai Dependency (lib/services/t2i/fal.ts:10)
The FAL provider imports @fal-ai/client but this package is not in package.json. This will cause runtime errors.

Fix: Add to package.json or mark FAL provider as truly unimplemented.

2. Hardcoded Model in API Response (app/api/flashcards/[cardId]/images/route.ts:190)
Model is hardcoded as dall-e-3 instead of using result.metadata?.model. This will show incorrect data when using FAL or other providers.

3. Storage Policy Too Permissive (supabase/migrations/20260108_flashcard_images_storage_policies.sql:10-13)
The INSERT policy allows any authenticated user to upload to any path in the bucket. Users could upload to other users directories.

Fix: Add path restriction WITH CHECK (bucket_id = 'flashcard-images' AND (storage.foldername(name))[1] = auth.uid()::text)

4. Missing Provider Availability Check (app/api/flashcards/[cardId]/images/route.ts:159)
The API does not check if the requested provider is available before attempting generation. Users could request fal without FAL_API_KEY set.


🟡 Medium Priority Issues

5. Race Condition in First Image Detection (route.ts:175-181)
If two requests generate images simultaneously, both could see count === 0 and set is_preferred = true.

Better approach: Always insert with is_preferred: true and let the trigger handle deduplication.

6. Missing Error Handling for Image Fetch (lib/services/t2i/index.ts:109)
DALL-E URLs are temporary (expire after ~1 hour). If there is a delay between generation and upload, this could fail without retry logic.

Recommendation: Add exponential backoff retry (2-3 attempts).

7. Potential Memory Issue with Large Images (index.ts:114-116)
Loads entire image into memory. For HD images (can be 2-5MB), this could be problematic at scale.

Consider: Stream directly to Supabase Storage if possible, or add image size limits.


🟢 Minor Issues & Suggestions

8. Inconsistent Language Name Mapping
Duplicated in prompt-generator.ts and app/api/translate/route.ts.
Suggestion: Extract to lib/constants/languages.ts for single source of truth.

9. Missing Cost Warning
DALL-E 3 HD images cost $0.12 each. Users could rack up costs quickly.
Consider: Show estimated cost before generation, rate limiting, subscription tier gating.

10. Regex Extraction Could Be More Robust (route.ts:138-143)
Only extracts the first cloze deletion when multiple may exist in one card.

@claude
Copy link

claude bot commented Jan 14, 2026

🔍 Performance Considerations

Good:

  • Denormalized has_image flag on flashcards table for fast filtering
  • Proper indexes on foreign keys and preferred images
  • Provider caching in factory
  • Image loading is lazy (only loads when card is displayed)

Could Improve:

  • Add CDN caching headers when serving images from Supabase Storage
  • Add image compression (DALL-E 3 returns PNGs which can be large)
  • Consider lazy-loading the generation buttons

📋 Test Coverage Assessment

Well Covered:

  • T2I provider instantiation and cost estimation
  • API endpoint validation (Zod schemas)
  • E2E UI interactions
  • Database triggers

Missing Coverage:

  • No tests for uploadImageToStorage and deleteImageFromStorage
  • No tests for the storage path regex extraction
  • No tests for generateImagePrompt with various card types
  • No tests for FAL provider

🎯 Recommendation

Approve with Required Changes:

Must Fix Before Merge:

  1. Fix storage RLS policy (security issue perf(ci): optimize Dockerfile layer caching order #3)
  2. Fix hardcoded model name (issue perf(ci): add .dockerignore to reduce build context size #2)
  3. Add @fal-ai/client to package.json OR remove FAL import (issue Tests #1)
  4. Add provider availability check in API (issue perf(ci): implement Docker Buildx with registry layer caching #4)

Should Fix Soon (can be follow-up PRs):


📊 Overall Assessment

Category Rating Notes
Architecture ⭐⭐⭐⭐⭐ Excellent provider abstraction and separation of concerns
Security ⭐⭐⭐⭐ Good RLS, one storage policy issue
Code Quality ⭐⭐⭐⭐ Clean, well-documented, follows project conventions
Testing ⭐⭐⭐⭐ Good coverage, could add more integration tests
Performance ⭐⭐⭐⭐ Proper indexing and denormalization
UX ⭐⭐⭐⭐⭐ Excellent versioning system and intuitive UI

Overall: 4.5/5 stars - This is a well-crafted feature that demonstrates strong engineering practices. Fix the critical issues and it is ready to ship!


Great work on this PR! The architecture is particularly impressive - the provider abstraction will make it easy to add more T2I providers in the future. 🚀

@Peleke Peleke merged commit 6f764b5 into staging Jan 14, 2026
3 checks passed
@Peleke Peleke deleted the feature/flashcard-images branch January 14, 2026 21:00
@claude
Copy link

claude bot commented Jan 14, 2026

Code Review - PR #233: AI Image Generation for Flashcards

This is an excellent, well-architected feature implementation! The code quality is high, with strong separation of concerns, comprehensive testing, and proper security practices. Here's my detailed analysis:


✅ Strengths

1. Excellent Architecture & Design Patterns

  • Provider abstraction: The T2IProvider interface is beautifully designed with swappable backends (DALL-E, FAL, Qwen, local). This follows the strategy pattern perfectly.
  • Clean separation of concerns: Services, API routes, components, and database are well-organized.
  • LLM-powered prompt generation: Smart use of GPT-4o-mini to convert flashcard content into optimal image prompts.
  • Cost estimation built-in: The estimateCost() method in providers is forward-thinking.

2. Strong Security Posture

  • Row Level Security (RLS): All policies correctly verify ownership through the flashcard → deck → user chain.
  • Storage policies: Proper authentication checks and ownership validation (auth.uid() = owner).
  • Input validation:
    • API routes use Zod schemas (GenerateImageSchema) for request validation.
    • ALLOWED_FIELDS whitelist in preferences API prevents mass assignment vulnerabilities.
  • No SQL injection risks: Parameterized queries via Supabase client.

3. Robust Database Design

  • Versioning support: Multiple images per card with is_preferred flag.
  • Denormalized optimization: has_image flag on flashcards with triggers for fast filtering.
  • Provider tracking: Stores provider, model, and generation params for debugging.
  • Atomic operations: ensure_single_preferred_image() trigger prevents race conditions.

4. Test Coverage

  • 35 passing unit tests for T2I services (DallE3Provider, prompt generator, factory).
  • 15 E2E test cases for UI interactions (audio autoplay, image generation, modal).
  • Tests properly skip when prerequisites aren't met (no cards, auth required).

5. UX Considerations

  • Progressive enhancement: Works without images, optional feature.
  • Audio autoplay opt-in: Defaults to false (less intrusive), persists to DB + localStorage.
  • Style preferences: Three options (illustrated/realistic/minimalist) with clear descriptions.
  • Image versioning UI: Clean modal for browsing/selecting/deleting versions.

🔍 Issues & Recommendations

Critical Issues: None ✅

High Priority

1. Storage Cleanup on Image Delete (app/api/flashcards/[cardId]/images/[imageId]/route.ts:133-139)

if (storagePathMatch) {
  try {
    await deleteImageFromStorage(storagePathMatch[1])
  } catch (storageError) {
    console.warn('Failed to delete from storage:', storageError)
    // Continue with DB deletion even if storage fails
  }
}

Issue: If storage deletion fails but DB deletion succeeds, you'll have orphaned files in storage that continue to consume space and cost money.

Recommendation: Consider either:

  • Fail the entire operation if storage deletion fails (simpler, safer).
  • Implement a cleanup job to periodically remove orphaned storage files.
  • Add the storage path to a "pending deletion" table for retry.

2. Rate Limiting Missing

Issue: No rate limiting on expensive image generation endpoints. A user could spam image generation and rack up OpenAI costs.

Recommendation: Add rate limiting:

// Example: Max 10 image generations per user per hour
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '1 h'),
})

Or simpler approach: Check count of images created in last hour:

const { count } = await supabase
  .from('flashcard_images')
  .select('*', { count: 'exact', head: true })
  .gte('created_at', new Date(Date.now() - 3600000).toISOString())

if (count && count >= 10) {
  return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 })
}

Medium Priority

3. Prompt Injection Risk (lib/services/t2i/prompt-generator.ts:92-96)

const userPrompt = `Create an image prompt for this ${languageName} flashcard:

Front (${languageName}): ${front}
Back (English): ${back}

Issue: User-controlled flashcard content (front, back) is directly interpolated into LLM prompt. While not critical (you're not executing code), a malicious user could craft flashcard content to manipulate the LLM output or leak system prompt.

Recommendation: Add content sanitization or length limits:

const sanitize = (text: string) => text.slice(0, 500).replace(/[<>]/g, '')
Front: ${sanitize(front)}
Back: ${sanitize(back)}

4. Error Handling in Image Upload (lib/services/t2i/index.ts:108-116)

const response = await fetch(imageUrl)
if (!response.ok) {
  throw new Error(`Failed to fetch image: ${response.statusText}`)
}

Issue: If DALL-E returns a temporary URL that expires quickly, the fetch might fail. No retry logic.

Recommendation: Add retry with exponential backoff or immediately upload after generation (before returning to client).

5. Missing Input Validation (app/api/flashcards/[cardId]/images/route.ts:144-148)

if (flashcard.card_type === 'cloze' && flashcard.cloze_text) {
  front = flashcard.cloze_text.replace(
    /\{\{c\d+::([^}]+)(?:::([^}]+))?\}\}/g,
    '$1'
  )

Issue: Regex parsing of user content without validation. Malformed cloze syntax could cause unexpected behavior.

Recommendation: Add try-catch and fallback:

try {
  front = flashcard.cloze_text.replace(...)
} catch {
  front = flashcard.cloze_text // Use raw text as fallback
}

6. Hardcoded Provider in UI (components/flashcards/GenerateImageButton.tsx:43)

body: JSON.stringify({
  provider: 'dalle3',  // Hardcoded
  ...
})

Issue: Always uses DALL-E even though you have provider abstraction.

Recommendation: Respect user's preferred_image_provider from profile:

const [preferences, setPreferences] = useState(null)
useEffect(() => {
  fetch('/api/user/preferences').then(r => r.json()).then(setPreferences)
}, [])

const provider = preferences?.preferred_image_provider || 'dalle3'

Low Priority

7. Memory Leak Potential (app/flashcards/review/page.tsx:183)

await new Promise((resolve) => setTimeout(resolve, index * 100))

Issue: In generateBatchImagePrompts, if component unmounts during batch operation, timeouts continue.

Recommendation: Track and clear timeouts on unmount (or this might be server-side only, in which case it's fine).

8. Missing Alt Text Accessibility (components/flashcards/ImageVersionModal.tsx:185)

<Image
  src={image.image_url}
  alt="Flashcard image"  // Generic alt text
/>

Recommendation: Use the prompt or flashcard content for alt text:

alt={image.prompt.slice(0, 100) || `Image for ${cardId}`}

9. Test Mocking Incomplete

The E2E tests skip image generation to avoid costs, but this means the actual DALL-E integration isn't tested end-to-end in CI.

Recommendation: Consider adding a staging environment test with a small budget or mock the OpenAI SDK in E2E tests.


📊 Performance Considerations

Good

  • Indexes on foreign keys: idx_flashcard_images_flashcard_id, idx_flashcard_images_preferred.
  • Optimistic updates: Audio autoplay saves to localStorage first, then syncs to DB.
  • Image caching: Storing images in Supabase Storage instead of hitting DALL-E repeatedly.

Potential Improvements

  • Lazy loading images: The review page loads images for current card only (useEffect at line 86) - good!
  • CDN for images: Ensure Supabase Storage is CDN-backed (it is by default).
  • Batch image generation: The generateBatchImagePrompts function exists but isn't exposed in UI yet.

🔒 Security Summary

Category Status Notes
Authentication ✅ Pass All routes check auth.getUser()
Authorization ✅ Pass RLS policies enforce ownership
Input Validation ⚠️ Good Zod validation on API, minor improvements possible
SQL Injection ✅ Pass Parameterized queries via Supabase
XSS ✅ Pass React auto-escapes, Next.js Image component used
Rate Limiting ❌ Missing No protection against API abuse
Error Handling ✅ Good Try-catch blocks, no sensitive data leaked

📝 Code Quality Observations

Excellent

  • TypeScript usage: Strong typing throughout, proper interfaces.
  • Comments: Migration files have excellent documentation.
  • Naming: Clear, descriptive names (e.g., ensure_single_preferred_image).
  • Error messages: Helpful and specific (not just "Error").

Minor Nitpicks

  • Console logs: Several console.error() calls - consider structured logging (e.g., Sentry).
  • Magic numbers: await page.waitForTimeout(500) in tests - consider constants.
  • Hardcoded strings: "Illustrated", "Realistic" labels could be i18n-ready.

🎯 Recommendations Summary

Must Fix Before Merge

  1. Add rate limiting to image generation endpoint (cost protection).

Should Fix Soon

  1. Improve storage deletion error handling (prevent orphaned files).
  2. Sanitize user input in prompt generation (defense in depth).
  3. Use user's preferred provider instead of hardcoding 'dalle3'.

Nice to Have

  1. Retry logic for image URL fetching.
  2. Better error handling for cloze regex parsing.
  3. Improved accessibility (alt text).
  4. Structured logging instead of console.error.

🎉 Conclusion

This is production-ready code with only one critical gap (rate limiting). The architecture is solid, testing is comprehensive, and security is well-implemented. The feature adds genuine value (visual memory aids) and the UX is polished.

Recommendation: ✅ Approve with minor changes

Add rate limiting before deploying to production, then merge. The other recommendations can be addressed in follow-up PRs.

Great work! 🚀


📚 Additional Notes

  • The workflow files (.github/workflows/claude*.yml) are interesting - automated Claude Code review! Meta. 🤖
  • Love the UAT instructions in the PR description - very thorough.
  • The migration rollback story isn't documented - consider adding DROP TABLE IF EXISTS scripts for emergencies.

Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com

@Peleke Peleke mentioned this pull request Jan 14, 2026
6 tasks
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.

1 participant