feat(reader): Text selection → translation with flashcard creation#230
feat(reader): Text selection → translation with flashcard creation#230
Conversation
- Add /api/translate endpoint using GPT-4o-mini for translation - Add useTextSelection hook for detecting highlighted text in container - Add TranslationSheet component (responsive: bottom sheet mobile, sidebar desktop) - Integrate translation into TextRenderPanel with mutual exclusion from DefinitionSidebar - Fix Dialog component to use createPortal (escapes parent stacking contexts) - Add E2E tests for translation feature (12 test cases) Features: - Highlight text in reader → get AI translation - Audio playback of source text (TTS) - Create flashcard from translation (front=source, back=translation) - Escape key or X button closes sheet - Word click closes translation, opens definition (mutual exclusion) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PR Review: Reader Translation FeatureOverviewThis PR adds text selection-based translation to the reader with flashcard creation. The implementation is well-structured and follows the project's established patterns. Below are my findings organized by category. ✅ Strengths
🔴 Critical Issues1. Missing Authentication Check in Translation APIFile: The translation endpoint doesn't verify user authentication. All other API routes in the codebase use Risk: Unauthenticated users could abuse the translation API, leading to unexpected OpenAI costs. Fix: import { createClient } from '@/lib/supabase/server'
export async function POST(request: Request) {
// Add authentication check
const supabase = await createClient()
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
// ... rest of the implementation
}2. Potential Memory Leak in useTextSelection HookFile: The hook sets up event listeners on Issue: If a user rapidly navigates or the component remounts while a timeout is pending, Fix: Add a mounted ref or use AbortController pattern: useEffect(() => {
if (disabled) {
setSelectedText(null)
return
}
let mounted = true
// ... existing code ...
const handleMouseUp = () => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
if (mounted) handleSelectionChange()
}, 10)
}
// ... rest of implementation ...
return () => {
mounted = false
document.removeEventListener('mouseup', handleMouseUp)
document.removeEventListener('touchend', handleTouchEnd)
if (timeoutId) clearTimeout(timeoutId)
}
}, [containerRef, minLength, maxLength, disabled])
|
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request Review: Text Selection → Translation with Flashcard CreationOverviewThis PR adds a well-designed text selection → translation feature to the reader. The implementation is solid with good separation of concerns, comprehensive E2E tests, and proper error handling. Below are my observations and recommendations. ✅ Strengths1. Architecture & Code Quality
2. Error Handling
3. Test Coverage
4. UX Design
🔴 Issues & Concerns1. SECURITY: Missing Authentication CheckLocation: app/api/translate/route.ts:21 The API lacks authentication checks. Any unauthenticated user could call this endpoint and consume OpenAI credits. Recommendation: Add Supabase auth check at the start of the POST handler. 2. PERFORMANCE: No Translation CachingLocation: app/api/translate/route.ts:31-45 Unlike the definition sidebar which uses DictionaryCache, translations are fetched fresh every time. This unnecessarily increases costs and latency. Recommendation: Implement translation caching similar to dictionary caching. 3. UX: Text Selection FeedbackLocation: lib/hooks/useTextSelection.ts:35-60 When selection exceeds length limits (500 chars), there's no visual feedback. Users might be confused why nothing happens. Recommendation: Add toast notification when selection exceeds limits. 4. ACCESSIBILITY: Missing ARIA Labels
|
| Category | Score | Notes |
|---|---|---|
| Code Quality | 8/10 | Clean, well-structured, good patterns |
| Security | 6/10 | Missing auth check (critical) |
| Performance | 7/10 | No caching for translations |
| Test Coverage | 9/10 | Excellent E2E coverage |
| Accessibility | 6/10 | Missing ARIA labels, focus management |
| Documentation | 8/10 | Good PR description, clear code |
Overall: Strong implementation with some important security and performance issues to address. The architecture is solid and the feature is well-tested. Fix the authentication issue before merging.
✨ Positive Notes
- Excellent use of AbortController for cleanup
- Great test coverage with realistic scenarios
- Smart reuse of existing components
- Clean separation of concerns with custom hook
- Responsive design that works across devices
- Proper TypeScript usage throughout
Great work overall! The feature adds real value to the platform. Address the security issue and this will be ready to ship.
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>
Summary
Changes
app/api/translate/route.ts- GPT-4o-mini translation endpointlib/hooks/useTextSelection.ts- Text selection detection hookcomponents/reader/TranslationSheet.tsx- Translation UIcomponents/reader/TextRenderPanel.tsx- Integrationcomponents/ui/dialog.tsx- Portal fix (createPortal to body)tests/e2e/reader/translation.spec.ts- 12 E2E test casesTest plan
/reader, enter Spanish/Latin text, click "Render"npx playwright test tests/e2e/reader/translation.spec.ts🤖 Generated with Claude Code