Skip to content

feat: Grammar Substrate Foundation (Epic #204)#226

Merged
Peleke merged 517 commits intomainfrom
feature/grammar-substrate
Jan 8, 2026
Merged

feat: Grammar Substrate Foundation (Epic #204)#226
Peleke merged 517 commits intomainfrom
feature/grammar-substrate

Conversation

@Peleke
Copy link
Owner

@Peleke Peleke commented Jan 7, 2026

Summary

Implements the Grammar Substrate Foundation (Epic #204) with a graph-ready architecture supporting Latin, Gothic, and Old Norse.

Architecture Highlights

  • Graph-ready schema: Designed for future migration to Memgraph
  • Multi-language: ISO 639 codes (la, got, non) with easy extensibility
  • Source tracking: Every concept cites A&G, Wright, EIEOL with section numbers
  • Leipzig glossing: Standard morphological annotation format

Database Schema (Nodes & Edges)

NODES                          EDGES
├── grammar_concepts           ├── grammar_relationships
├── lexicon_entries            ├── grammar_lexicon_links  
├── text_sources               └── lexicon_occurrences
└── text_occurrences

UI Components

  • /grammar - Index page with language tabs (🏛️ Latin, ⚔️ Norse, 📜 Gothic)
  • /grammar/[slug] - Detail page with breadcrumbs, examples, related concepts
  • Category filters (morphology, syntax, phonology)
  • CEFR difficulty badges (A1-C2)

Seeded Content

Language Sources Content
Latin Allen & Greenough, EIEOL 6 cases, 3 declensions, 2 conjugations
Gothic Wright's Grammar, EIEOL Alphabet, noun/verb classes
Old Norse EIEOL Umlaut, declensions, Vǫluspá analysis

Key Schema Features

  • grammar_concepts.source_refs - JSONB array of {source, section, url}
  • grammar_concepts.examples - JSONB array of {source, target, gloss}
  • lexicon_occurrences.morphological_gloss - Leipzig format (NOM.SG.F)
  • Helper functions: get_grammar_tree(), get_related_concepts()

Test Plan

  • Run migrations on staging
  • Browse /grammar - verify language tabs work
  • Check /grammar/la/cases/nominative detail page
  • Verify source citations display correctly
  • Test search and category filters

Related Issues

Next Steps (not in this PR)

  • Decomposition: Link Latin analyzer output to grammar concepts
  • More content: Additional grammar points from sources
  • Vocabulary: Populate lexicon_entries from EIEOL glossaries

🤖 Generated with Claude Code

Peleke Sengstacke and others added 30 commits November 16, 2025 18:09
…oncise 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>
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>
- 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>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…arding 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 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>
- 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>
## 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>
- 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)
- 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 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>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- 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 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>
✨ 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>
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>
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]
…rols

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>
- 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>
- 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>
…tifications

- 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>
- 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>
…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>
- Spanish: 'Ver Palabra del Día'
- Latin: 'Vide Verbum Diei'
- More authentic language experience in PWA
- 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>
- 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
- 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 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
Peleke Sengstacke and others added 21 commits December 22, 2025 11:29
- Add icons config to metadata pointing to /logo.png
- Update title and description to be language-agnostic

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add script to generate favicon sizes using sharp
- Generate 16x16, 32x32, 180x180, 192x192, 512x512 icons from logo
- Update layout metadata to use properly sized favicons
- Update PWA manifest with correct icon references

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add logo as favicon and navigation branding
Multi-language onboarding support (Spanish, Latin, Icelandic)
## Changes
- fix(tts): correct ElevenLabs API key env var name (ELEVEN_LABS_API_KEY → ELEVENLABS_API_KEY)
- fix(exercises): make translation direction detection language-agnostic
- fix(exercises): add language-aware feedback to Training Ground
- fix(ui): add dark mode support to Profile and Flashcards components
- fix(ui): eliminate extra scroll space on mobile

Closes #129, #130, #131

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

## Changes
- feat(flashcards): Cross-deck review mode for practicing cards from all decks
- feat(flashcards): XP system with gamification (streaks, levels)
- feat(flashcards): Improved review UI with card flipping animation
- fix(flashcards): Add missing cloze_count column to flashcards table
- fix(flashcards): Make XP animation subtle top-right toast (non-intrusive)

## Migrations
- 20251223_cross_deck_practice.sql
- 20251223_flashcard_ux_revamp.sql

🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Changes
- feat(pwa): Service worker for offline support and push notifications
- feat(pwa): Install banner for iOS/Android
- feat(wod): Add Icelandic language toggle to Word of Day page
- fix(wod): Supabase scope bug preventing unauthenticated fetches
- fix(wod): Dark mode styling for Word of Day page

## Migrations
- 20260107_add_icelandic_to_daily_words.sql

## Notes
- Push notifications require VAPID keys in production environment
- Supabase email templates for password reset need dashboard configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add lesson_stage_type enum (learn, read, practice, review, assess)
- Create lesson_stages table for configuring stage progression
- Create user_lesson_stage_progress table for tracking user progress
- Add RLS policies for stage access control
- Add helper functions: calculate_lesson_progress, get_next_stage
- Add TypeScript types for stages and progress data
- Add lib/stages utilities for stage management

Part of Epic #199 - Lesson Stage System

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
#202, #203)

Components:
- LessonStageViewer: Main container with stage navigation and progress tracking
- StageProgressBar: Visual progress indicator with stage dots
- StageProgressIndicator: Minimal progress bar for headers
- Stage content components: Learn, Read, Practice, Review, Assess

Features:
- Sequential stage progression with completion tracking
- "Pick N of M" exercise selection for Practice stage
- Time-based completion criteria for Learn stage
- Word click tracking for Read stage
- Score-based assessment with retry option

Tests:
- 20 unit tests for checkStageCompletion function
- All stage completion logic tested for each stage type

Config:
- Added coverage thresholds to vitest.config.ts (ratchet to prevent regression)
- Fixed duplicate test script in package.json

Part of Epic #199 - Lesson Stage System

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…#223)

- Add ViewModeToggle component with classic/hub/stages modes
- Integrate LessonStageViewer into LessonViewer for stages mode
- Create backfill migration for existing lessons
- Fetch and pass lesson stages to LessonViewer
- Update hub index to export ViewModeToggle

Part of Epic #220 - Stage System Integration

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Renamed migrations with numeric prefixes to ensure correct ordering:
- 20260107_01_lesson_stage_system.sql (schema first)
- 20260107_02_backfill_lesson_stages.sql (backfill second)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for the 5-stage lesson progression system:
- View Mode Toggle: 3-way toggle display and switching
- Stages View: Stage progression UI rendering
- Learn Stage: Time-based progress tracking
- Stage Navigation: Previous/Next button functionality
- Progress Persistence: Stage progress indicator display

All 7 tests passing:
- should display 3-way view toggle in lesson header
- should switch between Classic and Hub views
- should display stage progression UI when Stages view is selected
- should show time-based progress in Learn stage
- should navigate between stages using Previous/Next buttons
- should show stage progress indicator in header

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ProgressRing SVG component with smooth fill animation
- Position ring in top-right corner, out of content flow
- Show percentage in center, checkmark when complete
- Display elapsed time and target below ring
- Update button text to show countdown ("27s remaining...")
- Update E2E test selectors to match new UI

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

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

- Add breadcrumb navigation: Course > Lesson > Current Stage
- Integrate shimmer/gradient effects matching existing design system
- Add gradient accent line at header top
- Enhanced visual styling for lesson context and stage header
- Update E2E tests to match new header format (numbered circle + label)
- Add full stage completion flow E2E test with tutorial overlay handling
- Fix test selectors for new stage UI patterns

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Schema (graph-ready architecture)
- Enhanced `grammar_concepts` with language, slug, hierarchy, source_refs
- New `lexicon_entries` for language-agnostic vocabulary
- New `text_sources` for manuscripts/corpora (sagas, codexes, grammars)
- New `text_occurrences` for specific text passages with glossing
- Edge tables: `grammar_relationships`, `grammar_lexicon_links`, `lexicon_occurrences`
- Helper functions: `get_grammar_tree()`, `get_related_concepts()`

## UI
- `/grammar` index page with language tabs (Latin 🏛️, Old Norse ⚔️, Gothic 📜)
- `/grammar/[slug]` detail page with breadcrumbs, examples, related concepts
- Category filters (morphology, syntax, phonology)
- Search functionality
- CEFR difficulty badges

## Seed Content
### Latin (from Allen & Greenough, EIEOL)
- 6 cases with full paradigms and examples
- 3 declensions (1st, 2nd, 3rd)
- 2 conjugations (1st, 2nd)

### Gothic (from Wright's Grammar, EIEOL)
- Alphabet with Wulfila's script
- Noun declension classes (strong/weak)
- Verb classes (strong ablaut, weak dental)

### Old Norse (from EIEOL)
- Umlaut (i-umlaut, u-umlaut) with sound change tables
- Noun declensions with paradigms
- Verb classes
- Vǫluspá stanza 1 with full grammatical analysis

## Architecture Notes
- Designed for future graph DB migration (Memgraph)
- Source references tracked for all content
- Leipzig glossing format for morphological analysis
- Cross-language relationships for comparative linguistics

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Outlines the strategy for connecting Latin processor output to Grammar
Substrate schema:

Phase 1: Concept Matching
- Map processor GrammarConcept → grammar_concepts.slug
- Case/declension/conjugation detection rules

Phase 2: Vocabulary Linking
- Connect analyzed words to lexicon_entries
- Store morphological analysis

Phase 3: Text Occurrence Storage
- Store passages in text_occurrences
- Link tokens to lexicon_occurrences

Includes:
- UI integration flow (word click → grammar page)
- Database queries for cross-referencing
- Future Memgraph migration notes

🤖 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 7, 2026

PR Review: Grammar Substrate Foundation (Epic #204)

This is a substantial PR implementing a graph-ready grammar architecture. I've reviewed the schema design, API routes, UI components, and implementation patterns. Overall, this is solid foundational work with some areas for improvement.


🟢 Strengths

Architecture & Design

  • Excellent graph-ready schema design - The node/edge separation is well thought out and future-proofs for Memgraph migration
  • Multi-language support from day one - ISO 639 codes with clean extensibility
  • Source tracking - JSONB source_refs with section numbers provides proper academic citations
  • Leipzig glossing standard - Following established conventions for morphological annotation
  • Hierarchical structure - Parent-child relationships with recursive CTEs (get_grammar_tree) are well implemented

Database Schema (20260107_grammar_substrate.sql)

  • Comprehensive indexing strategy (language, slug, parent_id, category)
  • Proper foreign key constraints with ON DELETE CASCADE
  • Smart use of JSONB for flexible data (examples, source_refs, cognates)
  • Helper functions (get_grammar_tree, get_related_concepts) provide clean graph traversal
  • RLS policies are sensible (public read, authenticated write)

UI/UX

  • Clean breadcrumb navigation in detail pages (app/grammar/[slug]/page.tsx:122-163)
  • Good use of CEFR difficulty badges with appropriate color coding
  • Language tabs with emoji flags provide visual distinction
  • Responsive grid layout for concept cards

🟡 Issues & Concerns

1. Security: SQL Injection Risk (High Priority)

Location: app/api/grammar-concepts/search/route.ts:30

.or(`name.ilike.%${searchTerm}%,display_name.ilike.%${searchTerm}%`)

Issue: Direct string interpolation in query without sanitization could allow SQL injection.

Fix: Use Supabase's parameterized queries or escape the search term properly:

.or(`name.ilike.%${searchTerm.replace(/[%_]/g, '\\$&')}%,display_name.ilike.%${searchTerm.replace(/[%_]/g, '\\$&')}%`)

2. Security: Overly Permissive RLS Policies (Medium Priority)

Location: supabase/migrations/20251111_grammar_concepts_insert_policy.sql:15-35

Issue: All authenticated users can INSERT/UPDATE/DELETE grammar concepts. This is risky for production.

Recommendation: Implement role-based access:

-- Option 1: Add admin role check
CREATE POLICY "Only admins can create grammar concepts"
  ON public.grammar_concepts
  FOR INSERT
  TO authenticated
  WITH CHECK (
    EXISTS (
      SELECT 1 FROM user_roles 
      WHERE user_id = auth.uid() 
      AND role IN ('admin', 'content_editor')
    )
  );

-- Option 2: Use service role for seeding, no INSERT policy for regular users

3. Bug: Missing NULL Checks (Medium Priority)

Location: app/grammar/page.tsx:60-65

const categoryStats = (categoryCounts || []).reduce((acc, c) => {
  if (c.category) {
    acc[c.category] = (acc[c.category] || 0) + 1
  }
  return acc
}, {} as Record<string, number>)

Issue: If c.category is null in the database, it's correctly skipped here. However, in the UI rendering at line 168, you access CATEGORIES[category as keyof typeof CATEGORIES] which could return undefined if a concept has a category not in the CATEGORIES const.

Fix:

{CATEGORIES[category as keyof typeof CATEGORIES]?.name || category}
// Already handled correctly with || fallback, but could add safer typing

4. Performance: N+1 Query Pattern (Medium Priority)

Location: app/grammar/[slug]/page.tsx:82-100

Issue: Sequential queries for relationships:

  1. Get outgoing relationships
  2. Get incoming relationships
  3. Get linked vocabulary

Fix: Combine into a single query or use parallel Promise.all():

const [relatedOutgoing, relatedIncoming, linkedVocab] = await Promise.all([
  supabase.from('grammar_relationships')...
  supabase.from('grammar_relationships')...
  supabase.from('grammar_lexicon_links')...
])

5. Missing Error Handling (Low-Medium Priority)

Location: Multiple API routes

Example: app/api/grammar-concepts/route.ts:8-50

Issue: Generic error responses don't help debugging. Consider:

  • Logging error details server-side
  • Returning structured error responses
  • Using different status codes for different failure types

Example Fix:

if (error) {
  console.error("Failed to create grammar concept:", error);
  
  // Provide more specific errors
  if (error.code === '23505') { // unique violation
    return NextResponse.json(
      { error: "Grammar concept with this name already exists" },
      { status: 409 }
    );
  }
  
  return NextResponse.json(
    { error: "Failed to create grammar concept", details: error.message },
    { status: 500 }
  );
}

6. Type Safety Issues (Low Priority)

Location: app/grammar/[slug]/page.tsx:290, 304

{relatedOutgoing?.map((rel: any, i) => (

Issue: Using any type defeats TypeScript's purpose.

Fix: Define proper interfaces:

interface GrammarRelationship {
  relationship_type: string;
  target_concept?: {
    id: string;
    slug: string | null;
    display_name: string;
    language: string;
  };
  source_concept?: {
    id: string;
    slug: string | null;
    display_name: string;
    language: string;
  };
}

7. Missing Validation (Low Priority)

Location: app/api/grammar-concepts/route.ts:26

name: name.toLowerCase().replace(/\s+/g, "_"),

Issue: No validation for:

  • Name format (special characters, length)
  • Language code validity (should match ISO 639)
  • Slug uniqueness within language

Recommendation: Add Zod schema validation or at minimum regex checks.


🔵 Best Practices & Suggestions

1. Test Coverage

I don't see test files for the new grammar features. Consider adding:

  • Unit tests for API routes
  • Integration tests for the database schema (test relationships, helper functions)
  • E2E tests for the UI flows (browsing grammar, searching, navigation)

2. Documentation

The seed data file is excellent with its comments. Consider:

  • Adding JSDoc comments to API routes
  • Creating a docs/grammar-architecture.md explaining the node/edge design
  • Documenting the relationship types (prerequisite, related, contrast, etc.)

3. Accessibility

The UI looks good but could benefit from:

  • aria-label on search inputs
  • aria-current on active tab
  • Keyboard navigation for grammar concept cards
  • Focus visible states

4. Performance Optimization

For the grammar index page with potentially hundreds of concepts:

  • Consider pagination or virtual scrolling
  • Add loading="lazy" for images (if any added later)
  • Implement ISR (Incremental Static Regeneration) if content is mostly static

5. Migration Strategy

The migrations look solid, but:

  • Add rollback scripts (down.sql)
  • Consider adding IF NOT EXISTS checks to make migrations idempotent (you have some, but not all)
  • Document the migration order dependency

📊 Code Quality Metrics

  • PR Size: 828,869 additions, 7,279 deletions, 718 files changed
    • ⚠️ This is EXTREMELY large - consider breaking into smaller PRs in the future
  • SQL Quality: High (good indexing, proper constraints, helper functions)
  • TypeScript Quality: Medium (some any types, missing interfaces)
  • Error Handling: Medium (present but could be more specific)
  • Security: Medium (SQL injection risk, overly permissive RLS)

Test Plan Checklist

Before merging, ensure:

  • Manual testing of /grammar with all 3 languages
  • Test search functionality with special characters (%, _, quotes)
  • Verify breadcrumb navigation works for nested concepts
  • Test RLS policies (authenticated vs anonymous access)
  • Load test with 100+ concepts per language
  • Test relationship queries don't cause infinite recursion
  • Verify source citations display correctly
  • Mobile responsive testing

🎯 Recommendation

Conditional Approval - This is excellent foundational work, but please address:

Must Fix Before Merge:

  1. SQL injection vulnerability in search
  2. Add basic tests for critical paths

Should Fix Soon After:
3. Tighten RLS policies for production
4. Performance optimization (parallel queries)
5. Type safety improvements

The architecture is sound and the implementation is mostly solid. With the security fixes, this will be a great addition to the codebase.


📝 Minor Nits

  • app/grammar/page.tsx:11 - Gothic flag emoji might not render on all systems, consider fallback
  • Seed data has great content, but consider extracting to YAML/JSON for easier maintenance
  • Some console.error calls could use structured logging (consider a logging library)

Great work on this epic! The graph-ready design will pay dividends when you migrate to Memgraph. 🚀

@claude
Copy link

claude bot commented Jan 7, 2026

PR Review - Critical Issues Found

🚨 Major Concerns

1. Scope Mismatch - PR Title vs. Actual Changes

This PR is titled "Grammar Substrate Foundation" and claims to implement Epic #204, but contains 100 commits with features completely unrelated to grammar infrastructure:

  • ✅ Grammar-related: ~5 commits
  • ❌ Unrelated features: ~95 commits including:
    • PWA implementation with service workers and push notifications
    • Complete dashboard redesign with Lottie animations
    • Practice mode with "Duolingo-style" exercises
    • Word-of-day generation system
    • ElevenLabs audio integration in onboarding
    • Notification system with daily/welcome workflows
    • Multiple UI/UX polish commits

Impact: This violates fundamental version control best practices. Each feature should be in its own PR for:

  • Easier code review
  • Safer rollback if issues arise
  • Clear git history
  • Independent deployment/testing

Recommendation: Split this into separate PRs:

  1. Grammar substrate foundation (schema, migrations, types)
  2. PWA implementation
  3. Dashboard redesign
  4. Practice mode enhancements
  5. Notification system

2. Massive Size - 828,665 Additions Across 100+ Files

  • 100 files changed
  • 828K+ additions (exceeds GitHub's diff display limit)
  • 54 new API routes added
  • Only 4 test files for this volume of code

Impact:

  • Impossible to review thoroughly in reasonable time
  • High risk of introducing bugs that slip through review
  • Merge conflicts highly likely
  • CI/CD pipelines will struggle

Recommendation: Industry best practice is 200-400 lines per PR for thorough review. This PR is literally 2,000x that size.


3. Inadequate Test Coverage

For 828K lines and 54 new API endpoints:

  • Only 4 test files added
  • No E2E tests for new features visible
  • Critical paths like payment webhooks, push notifications, and grammar APIs appear untested

Security Risk: Untested API routes handling user data and payments are a major security vulnerability.

Recommendation: Each API route should have:

  • Unit tests for business logic
  • Integration tests for database operations
  • E2E tests for critical user flows

4. Commit Message Quality Issues

Several commits have concerning messages:

"🚀 FUCKING BEAUTIFUL AS REQUESTED! 🎨✨"
"🐛 DEBUG MODE ACTIVATED! Now we'll see what's going on!"
"🔥 EPIC CLEAN: Remove Annoying Hover Popups & Tooltip Delays"

Impact:

  • Unprofessional commit history
  • Difficult to generate changelogs
  • Makes git blame/bisect less useful
  • May indicate rushed development

Recommendation: Follow conventional commits format:

feat(grammar): add substrate schema and migrations
fix(auth): resolve enrollment button state bug

⚠️ Code Quality Concerns

5. Environment Variables Management

The .env.example file now requires 10+ third-party services:

  • Supabase (production + service role)
  • OpenAI API
  • Anthropic API
  • GitHub API (token + repo)
  • Stripe (4 keys + webhook secret)
  • ElevenLabs (implied)
  • VAPID keys for push notifications
  • Latin/Icelandic analyzer services

Issues:

  • No environment validation on startup
  • Missing documentation for which are truly required vs optional
  • Service role keys in .env pose security risk if accidentally committed

Recommendation:

  • Add runtime env validation (e.g., zod schema)
  • Document required vs optional clearly
  • Consider secrets manager for production

6. Database Migration Strategy Unclear

The PR description mentions "graph-ready schema" for future Memgraph migration but:

  • No migration files visible in the diff (too large to display)
  • Unclear how existing data will migrate
  • No rollback strategy documented

Recommendation:

  • Include migration plan in PR description
  • Test migrations on staging with production-like data
  • Document rollback procedures

7. Performance Concerns

New features added without apparent optimization:

  • Service worker caching strategies might be too aggressive (1 year for fonts)
  • No lazy loading visible for heavy Lottie animations
  • Push notification cron jobs have unclear rate limiting

Recommendation:

  • Add performance budgets to CI
  • Implement code splitting for heavy features
  • Document caching TTLs and rationale

🔒 Security Concerns

8. API Route Security

54 new API routes with unclear security posture:

  • Are all routes properly authenticated?
  • Do they implement rate limiting?
  • Is input validation comprehensive?
  • Are Supabase RLS policies enforced consistently?

Critical: Routes like /api/admin/* need authentication verification.

Recommendation: Security audit checklist for each route:

  • Authentication required
  • Authorization checks (RLS or explicit)
  • Input validation (Zod schemas)
  • Rate limiting configured
  • Error messages don't leak sensitive info

9. Push Notification System

New VAPID key management and push notification system:

  • Keys in .env.example (good for template)
  • Unclear how keys are rotated in production
  • User consent flow not visible in diff

Recommendation:

  • Document key rotation procedure
  • Ensure GDPR/privacy compliance
  • Add opt-out mechanism visibility

📊 Test Coverage

10. Missing Critical Tests

Given the scope, these should exist but aren't visible:

  • Grammar concept CRUD operations
  • Lexicon entry validation
  • Push notification delivery
  • PWA offline functionality
  • Payment webhook handling
  • Audio caching logic

Recommendation: Aim for:

  • 80%+ unit test coverage
  • Integration tests for all database operations
  • E2E tests for:
    • Grammar concept browsing
    • Practice mode flows
    • Subscription checkout
    • PWA installation

✅ Positive Notes

Despite the concerns above, there are some good architectural decisions visible:

  1. Type Safety: Zod schemas for validation (good practice)
  2. Service Separation: Latin analyzer as separate Python service (good architecture)
  3. Caching Strategy: ElevenLabs TTS caching to reduce costs (smart)
  4. Documentation: Multiple .md files for features (helpful for onboarding)
  5. CI/CD Enhancements: [skip ci] support for docs (sensible)

Summary Recommendations

Before Merging:

  1. [CRITICAL] Split into separate feature PRs (5-7 PRs recommended)
  2. [CRITICAL] Add comprehensive test coverage (target 80%+)
  3. [CRITICAL] Security audit of all 54 API routes
  4. [HIGH] Document database migrations and rollback plan
  5. [HIGH] Clean up commit messages to follow conventional commits
  6. [MEDIUM] Add environment variable validation
  7. [MEDIUM] Performance testing for new features
  8. [LOW] Add CODEOWNERS file for future reviews

Review Philosophy:

Per the repository's CLAUDE.md emphasis on "professional objectivity" and "technical accuracy," this review prioritizes safety and maintainability over speed to production.

Current State: Not ready to merge due to scope, testing, and security concerns.

Estimated Effort to Address: 40-60 hours to properly split, test, and secure this work.

Would you like me to help create a breakdown of how to split this into reviewable PRs? I can analyze the commits and suggest logical groupings.

Unit tests (156 tests):
- GrammarIndexClient component logic (31 tests)
- Grammar concepts schema and validation (50 tests)
- Grammar database query patterns (40 tests)
- Schema validation and graph-ready patterns (35 tests)

E2E tests (Playwright):
- Grammar index page navigation and filtering
- Grammar detail page content and navigation
- Multi-language switching (Latin, Old Norse, Gothic)
- Category filtering and search functionality
- Unicode/special character handling
- Breadcrumb navigation
- Related concepts and linked vocabulary
- Source references display
- Responsive design verification

All unit tests passing: 156/156

🤖 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 7, 2026

Pull Request Review: Grammar Substrate Foundation (Epic #204)

This is an impressive implementation of a multi-language grammar system with a well-designed graph-ready architecture. Overall, the code quality is high with excellent documentation, comprehensive tests, and thoughtful design patterns.


Strengths

1. Excellent Database Design

  • Graph-ready architecture: Node/edge table structure well-designed for future migration to graph databases
  • Multi-language support: Clean ISO 639 language codes with proper indexing
  • Source tracking: JSONB source_refs and examples provide excellent scholarly citations (Allen & Greenough, EIEOL, Wright)
  • Hierarchical structure: Parent-child relationships with proper recursive SQL functions (get_grammar_tree, get_related_concepts)
  • Proper indexing: Composite indexes on (language, slug), (language, category, subcategory)

2. Security - RLS Policies

  • Public read access: All grammar tables have proper RLS enabled with public SELECT policies
  • No SQL injection risks: All API routes use Supabase parameterized queries
  • Input validation: Grammar concept creation validates required fields

3. UI/UX Quality

  • Excellent responsive design: Mobile-first approach with proper Tailwind breakpoints
  • Accessibility: Semantic HTML, proper heading hierarchy, ARIA-friendly navigation
  • CEFR difficulty badges: Color-coded A1-C2 levels with dark mode support
  • Breadcrumb navigation: Clean multi-level navigation
  • Leipzig glossing: Proper morphological annotation format in examples

4. Test Coverage

  • Comprehensive E2E tests: 7 test files covering index, detail, multi-language scenarios
  • Well-structured tests: Tests for language switching, category filtering, search, responsive design, accessibility
  • Realistic test scenarios: Tests handle edge cases gracefully with test.skip()

5. Code Quality

  • TypeScript safety: Proper typing with Next.js 15 patterns (async params)
  • Clean separation: Server components for data fetching, client components for interactivity
  • React best practices: useTransition for non-blocking navigation, proper form handling

Issues and Recommendations

Critical Issues

1. Missing User Authentication Check in POST Endpoints

Location: app/api/grammar-concepts/route.ts:8-50, app/api/lessons/[lessonId]/grammar/route.ts:53-109

The POST endpoints for creating grammar concepts don't verify user authentication or authorization. Anyone can create grammar content without being logged in.

Recommendation: Add authentication checks and role verification before allowing INSERT operations. Also need RLS policies for INSERT/UPDATE/DELETE that check for admin roles.

2. Potential XSS in Markdown Content

Location: app/grammar/[slug]/page.tsx:218

The grammar concept content is rendered as Markdown without HTML sanitization. If an attacker gains access to create content, they could inject malicious HTML/JavaScript.

Recommendation: Use rehype-sanitize plugin with ReactMarkdown to sanitize HTML output.

Medium Priority

3. Missing Error Handling for Database Functions

Location: supabase/migrations/20260107_grammar_substrate.sql:267-335

The get_grammar_tree and get_related_concepts functions don't handle circular references or infinite recursion.

Recommendation: Add max depth guards to prevent infinite loops.

4. N+1 Query Problem in Detail Page

Location: app/grammar/[slug]/page.tsx:64-113

The detail page makes 6 separate database queries (main concept, parent, children, outgoing relationships, incoming relationships, linked vocab).

Recommendation: Combine into fewer queries using Supabase nested selects for better performance.

5. Missing Slug Generation for Old Concepts

Location: supabase/migrations/20260107_grammar_substrate.sql:18-28

The migration adds slug as nullable but doesn't backfill slugs for existing concepts.

Recommendation: Add a migration step to generate slugs for existing records and make the column NOT NULL.

6. Search Query Injection Risk

Location: app/grammar/page.tsx:48

The search uses .ilike with user input interpolation. Special characters like % and _ are wildcard characters in ILIKE and could cause unexpected behavior.

Recommendation: Escape special characters or use PostgreSQL full-text search instead.

Low Priority

7. Performance: Missing Limit on Grammar Tree Function

The get_grammar_tree function could return unbounded results. Consider adding pagination.

8. Type Safety: Missing TypeScript Types for JSONB Fields

The source_refs, examples, definitions, and cognates JSONB fields should have TypeScript interfaces or Zod schemas for better type safety.

9. Accessibility: Missing aria-label on Search Input

10. Code Duplication: Language Metadata

The LANGUAGES constant is duplicated in app/grammar/page.tsx and app/grammar/[slug]/page.tsx. Extract to shared constant file.


Performance Considerations

Good:

  • Proper database indexes on frequently queried columns
  • Efficient recursive CTEs for hierarchical queries

Needs Attention:


Test Coverage Assessment

Excellent coverage with 7 E2E test files and multiple unit tests for grammar queries and schema.

Missing tests:

  • API route tests for POST endpoints
  • Error handling tests (database failures, malformed input)
  • Security tests (unauthorized access attempts)

Architecture Notes

Positive Patterns:

  1. Graph-ready design - excellent forward planning for Memgraph migration
  2. Scholarly rigor - source citations with section numbers
  3. Multi-language support from day one
  4. Separation of reference data (grammar_concepts) vs user data (vocabulary)

Potential Concerns:

  1. Large seed file - consider breaking into separate migration files per language
  2. JSONB fields need documented schema structure for future maintainability

Summary

This is a solid, well-architected PR with excellent documentation and test coverage. The main concerns are:

  1. Critical: Add authentication/authorization to POST endpoints
  2. Critical: Sanitize Markdown HTML to prevent XSS
  3. Medium: Optimize N+1 queries on detail page
  4. Medium: Escape search query wildcards or use full-text search

After addressing the critical security issues, this is ready to merge. The foundation is excellent and will scale well as you add more languages and content.

Estimated effort to address critical issues: 2-4 hours


Great work on this epic! The graph-based approach and scholarly attention to detail (Leipzig glossing, proper citations) sets this apart from typical language learning platforms.

🤖 Generated with Claude Code

Peleke Sengstacke and others added 2 commits January 7, 2026 23:39
The ORDER BY clause referenced sort_order but it wasn't
included in the CTE's SELECT or RETURNS TABLE.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Ensures schema runs before seed:
- 20260107_03_grammar_substrate.sql
- 20260107_04_seed_grammar_content.sql

🤖 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: Grammar Substrate Foundation (Epic #204)

Executive Summary

This is an exceptionally well-architected PR that establishes a professional-grade linguistic reference system. The implementation demonstrates strong technical decisions, comprehensive testing, and academic rigor appropriate for a language learning platform.

Recommendation:Approve with minor suggestions


🎯 Strengths

1. Graph-Ready Architecture ⭐⭐⭐⭐⭐

The node/edge separation is exemplary:

  • Clear separation of nodes (grammar_concepts, lexicon_entries, text_sources, text_occurrences)
  • Well-defined edges (grammar_relationships, grammar_lexicon_links, lexicon_occurrences)
  • Future-proof for Memgraph migration while working efficiently in PostgreSQL
  • Recursive CTEs in helper functions (get_grammar_tree, get_related_concepts) are elegant

File: supabase/migrations/20260107_grammar_substrate.sql

2. Comprehensive Indexing Strategy ⭐⭐⭐⭐⭐

Every access pattern is covered:

  • Composite index on (language, slug) for URL routing
  • Category browsing: (language, category, subcategory)
  • Hierarchical queries: parent_id
  • Full-text search ready with proper text column indexing

Lines: supabase/migrations/20260107_grammar_substrate.sql:31-45

3. Academic Source Tracking ⭐⭐⭐⭐⭐

The source_refs JSONB structure is well-designed:

source_refs JSONB DEFAULT '[]'::jsonb -- [{source, section, url}]

Citations to Allen & Greenough, Wright, EIEOL demonstrate scholarly rigor.

4. Excellent Test Coverage ⭐⭐⭐⭐⭐

  • Schema tests validate all constraints and graph patterns
  • Query tests cover filtering, search, pagination, relationships
  • E2E tests for user-facing functionality
  • Tests document expected behavior effectively

Files:

  • tests/grammar/grammar-schema.test.ts
  • tests/grammar/grammar-queries.test.ts
  • tests/e2e/grammar/*.spec.ts

5. UI/UX Implementation ⭐⭐⭐⭐

  • Clean separation of server/client components
  • Language tabs with emoji flags (excellent UX touch)
  • CEFR difficulty badges with semantic colors
  • Breadcrumb navigation for context
  • Search + category filtering

Files: app/grammar/page.tsx, app/grammar/[slug]/page.tsx


🔍 Issues & Suggestions

Critical 🔴

1. API Security Gap: Missing Authentication/Authorization

File: app/api/grammar-concepts/route.ts:8-50

The POST endpoint creates grammar concepts without authentication:

export async function POST(request: Request) {
  // No auth check!
  const supabase = await createClient();
  // ...
}

Issue: Anyone can create grammar concepts, despite RLS being enabled.

Fix: Add authentication:

export async function POST(request: Request) {
  const supabase = await createClient();
  
  // Check authentication
  const { data: { user }, error: authError } = await supabase.auth.getUser();
  if (authError || !user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  
  // Optional: Check for admin role
  // const { data: profile } = await supabase
  //   .from('profiles')
  //   .select('role')
  //   .eq('id', user.id)
  //   .single();
  // if (profile?.role !== 'admin') {
  //   return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  // }
  
  // ... rest of the code
}

Impact: Security vulnerability allowing unauthorized content creation.


High Priority 🟠

2. SQL Injection Risk in Search Query

File: app/grammar/page.tsx:48

if (searchQuery) {
  query.or(`display_name.ilike.%${searchQuery}%,summary.ilike.%${searchQuery}%`)
}

Issue: While Supabase client libraries typically handle parameterization, this pattern could be risky if searchQuery contains special characters like , or ..

Fix: Use Supabase's safer query methods:

if (searchQuery) {
  query.or([
    `display_name.ilike.%${searchQuery}%`,
    `summary.ilike.%${searchQuery}%`
  ].join(','))
}

Or better yet, use multiple .or() calls:

if (searchQuery) {
  query.or(`display_name.ilike.%${searchQuery}%,summary.ilike.%${searchQuery}%`)
}

Impact: Potential SQL injection if user input is not properly sanitized.

3. Missing Write Policies in RLS

File: supabase/migrations/20260107_grammar_substrate.sql:237-260

All new tables have only SELECT policies:

CREATE POLICY "Anyone can view lexicon entries"
  ON public.lexicon_entries FOR SELECT
  USING (true);

Issue: No INSERT/UPDATE/DELETE policies defined. Without explicit write policies, writes will fail even for admins (when RLS is enabled).

Fix: Add admin write policies:

-- Example for lexicon_entries
CREATE POLICY "Admins can insert lexicon entries"
  ON public.lexicon_entries FOR INSERT
  TO authenticated
  WITH CHECK (
    EXISTS (
      SELECT 1 FROM public.profiles
      WHERE id = auth.uid() AND role = 'admin'
    )
  );

CREATE POLICY "Admins can update lexicon entries"
  ON public.lexicon_entries FOR UPDATE
  TO authenticated
  USING (
    EXISTS (
      SELECT 1 FROM public.profiles
      WHERE id = auth.uid() AND role = 'admin'
    )
  );

Impact: Content management features may not work even for authorized users.


Medium Priority 🟡

4. Inconsistent Slug Validation

File: app/api/grammar-concepts/route.ts:26

name: name.toLowerCase().replace(/\s+/g, "_"),

Issue: The API generates slug from name, but the migration shows slugs follow pattern {language}/{category}/{name} (e.g., la/cases/nominative).

Fix: Either:

  1. Generate full slug in API: slug: ${language}/${category}/${name.toLowerCase().replace(/\s+/g, '-')}
  2. Or document that the API is for internal use only and slugs are managed separately

Impact: Inconsistent data structure could break URL routing.

5. Missing Error Handling in Grammar Detail Page

File: app/grammar/[slug]/page.tsx:82-113

Multiple database queries without error handling:

const { data: relatedOutgoing } = await supabase
  .from('grammar_relationships')
  .select(...)
  .eq('source_concept_id', concept.id)

Fix: Add error handling and graceful degradation:

const { data: relatedOutgoing, error: relError } = await supabase
  .from('grammar_relationships')
  .select(...)
  .eq('source_concept_id', concept.id)

if (relError) {
  console.error('Failed to fetch relationships:', relError)
  // Continue rendering with empty relationships
}

Impact: Page could crash on database errors instead of degrading gracefully.

6. Performance: N+1 Query Potential

File: app/grammar/page.tsx:68-75

const { data: langCounts } = await supabase
  .from('grammar_concepts')
  .select('language')

Issue: Fetches ALL grammar concepts just to count languages. Could be expensive with thousands of concepts.

Fix: Use aggregation:

const { data: langCounts } = await supabase
  .rpc('get_language_counts') // Create a DB function

// Or use a more efficient count query
const { count: langCount } = await supabase
  .from('grammar_concepts')
  .select('language', { count: 'exact', head: true })

Impact: Slow page loads as content grows.


Low Priority 🟢

7. Accessibility: Missing ARIA Labels

File: components/grammar/GrammarIndexClient.tsx:94-116

Filter buttons lack ARIA labels for screen readers.

Fix:

<button
  key={key}
  onClick={() => handleCategoryClick(key)}
  disabled={count === 0}
  aria-label={`Filter by ${cat.name} (${count} concepts)`}
  aria-pressed={isActive}
  className={...}
>

8. Dark Mode Contrast

File: app/grammar/page.tsx:210-215

CEFR badge colors may have insufficient contrast in dark mode:

${concept.difficulty_level === 'A1' ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : ''}

Suggestion: Test with WCAG contrast checker and adjust opacity if needed.

9. TypeScript Strictness

File: app/grammar/[slug]/page.tsx:290-303

Using any types:

{relatedOutgoing?.map((rel: any, i) => (

Fix: Define proper types:

interface RelatedConcept {
  relationship_type: string;
  target_concept: {
    id: string;
    slug: string;
    display_name: string;
    language: string;
  };
}

{relatedOutgoing?.map((rel: RelatedConcept, i) => (

📊 Performance Considerations

Database Query Optimization

Good: Indexes cover all access patterns
⚠️ Watch: Language/category count queries will slow down with scale
💡 Suggestion: Consider materialized views for counts:

CREATE MATERIALIZED VIEW grammar_concept_stats AS
SELECT 
  language,
  category,
  COUNT(*) as concept_count
FROM grammar_concepts
WHERE is_published = true
GROUP BY language, category;

CREATE UNIQUE INDEX ON grammar_concept_stats(language, category);

Refresh on content updates.


🔒 Security Assessment

Area Status Notes
SQL Injection ⚠️ Needs Review Search query concatenation (see issue #2)
Authentication 🔴 Missing POST endpoints unprotected (see issue #1)
Authorization 🔴 Incomplete No write RLS policies (see issue #3)
XSS Protection Good ReactMarkdown with remarkGfm is safe
CSRF Protection Good Next.js handles this
Rate Limiting ℹ️ Not Applicable Consider for public API if exposed

🧪 Test Coverage Assessment

Excellent Coverage:

  • ✅ Schema validation (nodes, edges, constraints)
  • ✅ Query patterns (filtering, search, pagination)
  • ✅ Graph operations (relationships, traversal)
  • ✅ E2E user flows

Missing Tests:

  • ❌ API route integration tests (especially POST/auth)
  • ❌ Error handling scenarios
  • ❌ RLS policy tests (verify users can't write without permission)
  • ❌ Migration rollback tests

Recommendation: Add API integration tests before merging:

// __tests__/api/grammar-concepts.test.ts
describe('POST /api/grammar-concepts', () => {
  it('rejects unauthenticated requests', async () => {
    const res = await fetch('/api/grammar-concepts', {
      method: 'POST',
      body: JSON.stringify({ name: 'test', display_name: 'Test' })
    })
    expect(res.status).toBe(401)
  })
})

📝 Documentation & Code Quality

Strengths:

  • ✅ Excellent SQL comments and schema documentation
  • ✅ Clear TypeScript interfaces
  • ✅ Helpful table/column comments in migration
  • ✅ Well-structured component organization

Suggestions:

  • 📚 Add JSDoc comments to API routes explaining auth requirements
  • 📚 Document the slug format convention ({lang}/{category}/{name})
  • 📚 Add README in app/grammar/ explaining the feature

🚀 Migration & Deployment

Pre-Merge Checklist:

Post-Merge:

  • Monitor query performance for count queries
  • Set up monitoring/alerts for API errors
  • Consider caching strategy for frequently accessed concepts

💯 Overall Assessment

Code Quality: A
Architecture: A+
Testing: A-
Security: C (needs auth fixes)
Documentation: B+

Final Verdict: This is outstanding architectural work. The graph-ready design, comprehensive testing, and attention to linguistic detail are exemplary. Address the security issues (#1, #3) before merging, and consider the other suggestions for future improvements.

Action Items Before Merge:

  1. 🔴 Critical: Add authentication to POST /api/grammar-concepts
  2. 🔴 Critical: Add admin write RLS policies for all new tables
  3. 🟠 High: Review search query string interpolation safety
  4. 🟡 Medium: Add API integration tests

Great work! This establishes a solid foundation for the grammar reference system. 🎉


Reviewed using Claude Code - Following CLAUDE.md conventions

@claude
Copy link

claude bot commented Jan 8, 2026

PR Review: Grammar Substrate Foundation (Epic #204)

This is a substantial PR implementing a graph-ready grammar substrate for multi-language support. I've reviewed the schema design, UI components, and overall architecture. Here's my comprehensive feedback:


Strengths

1. Excellent Schema Design

  • Graph-ready architecture: The separation of nodes (grammar_concepts, lexicon_entries, text_sources, text_occurrences) and edges (grammar_relationships, grammar_lexicon_links, lexicon_occurrences) is well-designed for future Memgraph migration
  • Multi-language support: ISO 639 language codes with flexible extensibility
  • Proper indexing: Comprehensive index coverage for common queries (slug, language, category, composite indexes)
  • Helper functions: get_grammar_tree() and get_related_concepts() provide good abstractions for hierarchical and graph traversal queries

2. Good Security Practices

  • RLS policies properly implemented for all new tables
  • Public read access for grammar reference data (appropriate for educational content)
  • Authenticated users can CRUD grammar concepts (though see security concerns below)
  • Proper use of createClient() from lib/supabase/server in server components

3. Clean UI Implementation

  • Server-side rendering with proper async/await patterns
  • Good UX with language tabs, category filters, breadcrumb navigation
  • Responsive design with proper accessibility considerations
  • Proper use of CEFR difficulty badges (A1-C2)
  • ReactMarkdown with GitHub Flavored Markdown for content rendering

4. Comprehensive Testing

  • Schema validation tests cover all node and edge tables
  • Graph-ready pattern tests demonstrate future migration readiness
  • RLS policy tests validate security expectations
  • Index coverage tests ensure query performance

⚠️ Security Concerns

1. Overly Permissive RLS Policies (HIGH PRIORITY)

Location: supabase/migrations/20251111_grammar_concepts_insert_policy.sql:15-35

The current RLS policies allow any authenticated user to create, update, or delete grammar concepts:

CREATE POLICY "Authors can create grammar concepts"
  ON public.grammar_concepts
  FOR INSERT
  TO authenticated
  WITH CHECK (true);  -- ⚠️ No restrictions!

Risk: A malicious authenticated user could:

  • Delete all grammar content
  • Modify educational content with incorrect information
  • Create spam or inappropriate content

Recommendation: Implement role-based access control:

-- Option 1: Admin-only access
CREATE POLICY "Only admins can create grammar concepts"
  ON public.grammar_concepts
  FOR INSERT
  TO authenticated
  WITH CHECK (
    auth.jwt() ->> 'role' = 'admin' OR
    auth.jwt() ->> 'email' IN (
      SELECT email FROM admin_users
    )
  );

-- Option 2: Content approval workflow
ALTER TABLE grammar_concepts ADD COLUMN created_by UUID REFERENCES auth.users(id);
ALTER TABLE grammar_concepts ADD COLUMN is_published BOOLEAN DEFAULT false;

CREATE POLICY "Users can create draft grammar concepts"
  ON public.grammar_concepts
  FOR INSERT
  TO authenticated
  WITH CHECK (created_by = auth.uid() AND is_published = false);

CREATE POLICY "Only admins can publish grammar concepts"
  ON public.grammar_concepts
  FOR UPDATE
  TO authenticated
  USING (auth.jwt() ->> 'role' = 'admin')
  WITH CHECK (is_published = true);

2. SQL Injection Prevention (MEDIUM PRIORITY)

Location: app/grammar/page.tsx:48

The search query uses Supabase's parameterized query builder, which is good, but verify the ilike pattern is properly escaped:

if (searchQuery) {
  query.or(`display_name.ilike.%${searchQuery}%,summary.ilike.%${searchQuery}%`)
}

Recommendation: Verify Supabase client properly escapes the searchQuery variable. Consider adding input sanitization:

const sanitizedQuery = searchQuery.replace(/[%_]/g, '\\$&').trim()
if (sanitizedQuery) {
  query.or(`display_name.ilike.%${sanitizedQuery}%,summary.ilike.%${sanitizedQuery}%`)
}

3. Missing API Authentication (MEDIUM PRIORITY)

Location: app/api/grammar-concepts/route.ts:8-50

The POST endpoint creates grammar concepts but doesn't verify user authentication:

export async function POST(request: Request) {
  try {
    const supabase = await createClient();
    // ❌ No authentication check!
    const body = await request.json();

Recommendation: Add authentication check:

export async function POST(request: Request) {
  try {
    const supabase = await createClient();
    
    // Verify authentication
    const { data: { user }, error: authError } = await supabase.auth.getUser();
    if (authError || !user) {
      return NextResponse.json(
        { error: "Unauthorized" },
        { status: 401 }
      );
    }
    
    // Verify role (if implementing RBAC)
    const { data: profile } = await supabase
      .from('user_profiles')
      .select('role')
      .eq('user_id', user.id)
      .single();
    
    if (profile?.role !== 'admin') {
      return NextResponse.json(
        { error: "Forbidden" },
        { status: 403 }
      );
    }
    
    const body = await request.json();
    // ... rest of logic

🐛 Potential Bugs

1. Empty Seed File (HIGH PRIORITY)

Location: supabase/migrations/20260107_04_seed_grammar_content.sql

The seed file is completely empty (0 bytes), despite the PR description claiming "6 cases, 3 declensions, 2 conjugations" for Latin and content for Gothic and Old Norse.

Impact: The grammar pages will show "No grammar concepts yet" after migration.

Recommendation: Either:

  • Add the seed data to the migration file, OR
  • Update the PR description to clarify that seeding will happen separately, OR
  • Provide a separate seed script in the deployment documentation

2. Missing Error Boundaries (MEDIUM PRIORITY)

Location: app/grammar/page.tsx, app/grammar/[slug]/page.tsx

The pages don't have React error boundaries to catch rendering errors gracefully.

Recommendation: Add error boundaries or wrap in Next.js error handling:

// app/grammar/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="text-center">
        <h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
        <button onClick={reset} className="px-4 py-2 bg-primary text-white rounded">
          Try again
        </button>
      </div>
    </div>
  )
}

3. Missing Null Checks (LOW PRIORITY)

Location: app/grammar/[slug]/page.tsx:290-317

Accessing nested properties without null checks:

{relatedOutgoing?.map((rel: any, i) => (
  <Link
    href={`/grammar/${rel.target_concept?.slug || rel.target_concept?.id}`}
    // ⚠️ If target_concept is null, this will error

Recommendation: Add proper type definitions and null checks:

interface RelatedConcept {
  relationship_type: string
  target_concept: {
    id: string
    slug: string | null
    display_name: string
  } | null
}

{relatedOutgoing?.filter(rel => rel.target_concept).map((rel: RelatedConcept, i) => (
  <Link
    key={`out-${i}`}
    href={`/grammar/${rel.target_concept!.slug || rel.target_concept!.id}`}

🚀 Performance Considerations

1. Large PR Size (724 files)

This PR includes 832k additions across 724 files, making it difficult to review thoroughly. Many files appear unrelated to the grammar substrate (e.g., .serena/, .playwright-mcp/, EPIC-7-COMPLETION-SUMMARY.md).

Recommendation: Consider splitting into separate PRs:

  • PR 1: Grammar substrate schema and migrations (core)
  • PR 2: Grammar UI components
  • PR 3: Grammar API endpoints
  • PR 4: Documentation and test files

2. Database Query Optimization

Location: app/grammar/page.tsx:54-75

Multiple sequential database queries could be combined:

// Current: 3 separate queries
const { data: concepts } = await query
const { data: categoryCounts } = await supabase.from('grammar_concepts')...
const { data: langCounts } = await supabase.from('grammar_concepts')...

// Better: Use SQL aggregation
const { data } = await supabase.rpc('get_grammar_index_data', {
  language: selectedLang,
  category: selectedCategory,
  search: searchQuery
})

Create a stored procedure:

CREATE OR REPLACE FUNCTION get_grammar_index_data(
  p_language TEXT,
  p_category TEXT DEFAULT NULL,
  p_search TEXT DEFAULT NULL
)
RETURNS JSON AS $$
  -- Return concepts, category counts, and language counts in single query
$$ LANGUAGE sql STABLE;

3. Missing Pagination

Location: app/grammar/page.tsx:36-50

The grammar index fetches all concepts without pagination. This could become slow with hundreds of grammar concepts.

Recommendation: Implement cursor-based or offset pagination:

const PAGE_SIZE = 50
const offset = parseInt(params.offset || '0')

const query = supabase
  .from('grammar_concepts')
  .select('*', { count: 'exact' })
  .range(offset, offset + PAGE_SIZE - 1)

📝 Code Quality Suggestions

1. TypeScript Type Safety

Several places use any type:

Location: app/grammar/[slug]/page.tsx:290, 304

{relatedOutgoing?.map((rel: any, i) => (

Recommendation: Define proper interfaces (see null check section above)

2. Magic Numbers

Location: app/api/grammar-concepts/search/route.ts:18

const limit = parseInt(searchParams.get("limit") || "10");

Recommendation: Define constants:

const DEFAULT_SEARCH_LIMIT = 10
const MAX_SEARCH_LIMIT = 100

const rawLimit = parseInt(searchParams.get("limit") || String(DEFAULT_SEARCH_LIMIT))
const limit = Math.min(rawLimit, MAX_SEARCH_LIMIT) // Prevent abuse

3. Component Extraction

Location: app/grammar/page.tsx:206-220

The difficulty badge styling is repeated. Extract to component:

// components/grammar/DifficultyBadge.tsx
export function DifficultyBadge({ level }: { level: string }) {
  const colors = {
    A1: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
    A2: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
    // ... etc
  }
  
  return (
    <span className={`text-xs px-2 py-0.5 rounded-full ${colors[level] || ''}`}>
      {level}
    </span>
  )
}

🎯 Test Coverage

Missing Tests:

  1. API Route Tests: No tests for /api/grammar-concepts endpoints
  2. Integration Tests: No E2E tests for grammar pages
  3. RLS Policy Tests: Schema tests exist but no actual database tests

Recommendation: Add:

// tests/api/grammar-concepts.test.ts
describe('POST /api/grammar-concepts', () => {
  it('should reject unauthenticated requests', async () => {
    const response = await fetch('/api/grammar-concepts', {
      method: 'POST',
      body: JSON.stringify({ name: 'test', display_name: 'Test' })
    })
    expect(response.status).toBe(401)
  })
})

// tests/e2e/grammar.spec.ts
test('grammar index page loads', async ({ page }) => {
  await page.goto('/grammar')
  await expect(page.getByText('Grammar Reference')).toBeVisible()
})

📚 Documentation

Positive:

  • Excellent SQL comments in migration files
  • Comprehensive PR description
  • Good TypeScript JSDoc comments

Missing:

  • API documentation (OpenAPI/Swagger)
  • Migration rollback instructions
  • Performance benchmarks for helper functions

Architecture Praise

  1. Future-proofing: The graph-ready design shows excellent foresight
  2. Source tracking: JSONB source_refs with section numbers is well-designed
  3. Leipzig glossing: Using standard linguistic notation shows domain expertise
  4. Separation of concerns: Clear distinction between user vocabulary tracking and reference lexicon

Summary

Overall Assessment: This is a well-architected foundation with excellent schema design and clean UI implementation. However, there are critical security concerns that must be addressed before merging.

Must Fix Before Merge:

  1. Security: Implement proper RLS policies with role-based access control
  2. Security: Add authentication checks to API routes
  3. Bug: Add grammar seed data or update PR description

Should Fix Soon:

  1. ⚠️ Add error boundaries for graceful error handling
  2. ⚠️ Implement pagination for grammar index
  3. ⚠️ Add proper TypeScript types (remove any)

Nice to Have:

  1. 💡 Extract reusable components (DifficultyBadge)
  2. 💡 Add API route tests and E2E tests
  3. 💡 Optimize database queries with stored procedures

Recommendation: Request changes to address security concerns before merging. The architecture is solid, but the permissive RLS policies pose a significant risk in production.

Great work on the schema design and graph-ready architecture! 🎉


Review conducted following CLAUDE.md conventions for this repository.

@Peleke Peleke merged commit 80901bd into main Jan 8, 2026
4 checks passed
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.

Grammar point detail page Grammar index page with search/filter Grammar points table and RLS policies EPIC: Grammar Substrate Foundation

1 participant