# Phase 2.1: Rich Annotations & Highlighting - Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task with code reviews. **Goal:** Build a complete highlighting and annotation system that works offline-first with seamless sync. **Architecture:** Local-first storage in IndexedDB with automatic sync to backend. Highlights appear immediately, sync happens in background every 30 seconds. Uses timestamp-based conflict resolution for cross-device sync. **Tech Stack:** TypeScript, React, Material-UI, IndexedDB, Prisma (backend), TDD --- ## Task 1: Extend Types for Highlights **Files:** - Modify: `/root/biblical-guide/types/index.ts` - Test: `/root/biblical-guide/__tests__/types/highlights.test.ts` (create) **Step 1: Write failing test** Create file `/root/biblical-guide/__tests__/types/highlights.test.ts`: ```typescript import { BibleHighlight } from '@/types' describe('BibleHighlight types', () => { it('should create highlight with valid color', () => { const highlight: BibleHighlight = { id: 'test-id', verseId: 'verse-123', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'synced' } expect(highlight.color).toBe('yellow') }) it('should reject invalid color', () => { // This test validates TypeScript type checking // @ts-expect-error - 'red' is not a valid color const highlight: BibleHighlight = { id: 'test-id', verseId: 'verse-123', color: 'red', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'synced' } }) it('should validate syncStatus types', () => { const highlight: BibleHighlight = { id: 'test-id', verseId: 'verse-123', color: 'blue', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } expect(['pending', 'syncing', 'synced', 'error']).toContain(highlight.syncStatus) }) }) ``` **Step 2: Run test to verify it fails** ```bash npm test -- __tests__/types/highlights.test.ts ``` Expected output: FAIL - "BibleHighlight is not defined" **Step 3: Add types to `/root/biblical-guide/types/index.ts`** Add at the end of the file: ```typescript // Highlight system types export type HighlightColor = 'yellow' | 'orange' | 'pink' | 'blue' export type SyncStatus = 'pending' | 'syncing' | 'synced' | 'error' export interface BibleHighlight { id: string // UUID verseId: string userId?: string // Optional, added by backend color: HighlightColor createdAt: number // timestamp updatedAt: number // timestamp syncStatus: SyncStatus syncErrorMsg?: string } export interface HighlightSyncQueueItem { highlightId: string action: 'create' | 'update' | 'delete' highlight: BibleHighlight retryCount: number } export interface CrossReference { refVerseId: string bookName: string chapter: number verse: number preview: string } ``` **Step 4: Run test to verify it passes** ```bash npm test -- __tests__/types/highlights.test.ts ``` Expected output: PASS - all 3 tests pass **Step 5: Commit** ```bash git add types/index.ts __tests__/types/highlights.test.ts git commit -m "feat: add TypeScript types for highlights and sync system" ``` --- ## Task 2: Create Highlights IndexedDB Store **Files:** - Create: `/root/biblical-guide/lib/highlight-manager.ts` - Test: `/root/biblical-guide/__tests__/lib/highlight-manager.test.ts` (create) **Step 1: Write failing test** Create file `/root/biblical-guide/__tests__/lib/highlight-manager.test.ts`: ```typescript import { initHighlightsDatabase, addHighlight, getHighlight, getAllHighlights, deleteHighlight } from '@/lib/highlight-manager' import { BibleHighlight } from '@/types' describe('HighlightManager', () => { beforeEach(async () => { // Clear IndexedDB before each test const db = await initHighlightsDatabase() const tx = db.transaction('highlights', 'readwrite') tx.objectStore('highlights').clear() }) it('should initialize database with highlights store', async () => { const db = await initHighlightsDatabase() expect(db.objectStoreNames.contains('highlights')).toBe(true) }) it('should add a highlight and retrieve it', async () => { const highlight: BibleHighlight = { id: 'h-123', verseId: 'v-456', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } await addHighlight(highlight) const retrieved = await getHighlight('h-123') expect(retrieved).toEqual(highlight) }) it('should get all highlights', async () => { const highlights: BibleHighlight[] = [ { id: 'h-1', verseId: 'v-1', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' }, { id: 'h-2', verseId: 'v-2', color: 'blue', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'synced' } ] for (const h of highlights) { await addHighlight(h) } const all = await getAllHighlights() expect(all.length).toBe(2) }) it('should delete a highlight', async () => { const highlight: BibleHighlight = { id: 'h-123', verseId: 'v-456', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } await addHighlight(highlight) await deleteHighlight('h-123') const retrieved = await getHighlight('h-123') expect(retrieved).toBeNull() }) }) ``` **Step 2: Run test to verify it fails** ```bash npm test -- __tests__/lib/highlight-manager.test.ts ``` Expected output: FAIL - "highlight-manager module not found" **Step 3: Create highlight-manager.ts** Create file `/root/biblical-guide/lib/highlight-manager.ts`: ```typescript import { BibleHighlight } from '@/types' const DB_NAME = 'BiblicalGuide' const DB_VERSION = 2 // Increment version if schema changes const HIGHLIGHTS_STORE = 'highlights' let db: IDBDatabase | null = null export async function initHighlightsDatabase(): Promise { if (db) return db return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION) request.onerror = () => reject(new Error('Failed to open IndexedDB')) request.onsuccess = () => { db = request.result resolve(db) } request.onupgradeneeded = (event) => { const database = (event.target as IDBOpenDBRequest).result // Create highlights store if it doesn't exist if (!database.objectStoreNames.contains(HIGHLIGHTS_STORE)) { const store = database.createObjectStore(HIGHLIGHTS_STORE, { keyPath: 'id' }) // Index for finding highlights by syncStatus for batch operations store.createIndex('syncStatus', 'syncStatus', { unique: false }) // Index for finding highlights by verse store.createIndex('verseId', 'verseId', { unique: false }) } } }) } export async function addHighlight(highlight: BibleHighlight): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite') const store = tx.objectStore(HIGHLIGHTS_STORE) const request = store.add(highlight) request.onsuccess = () => resolve(request.result as string) request.onerror = () => reject(new Error('Failed to add highlight')) }) } export async function updateHighlight(highlight: BibleHighlight): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite') const store = tx.objectStore(HIGHLIGHTS_STORE) const request = store.put(highlight) request.onsuccess = () => resolve() request.onerror = () => reject(new Error('Failed to update highlight')) }) } export async function getHighlight(id: string): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly') const store = tx.objectStore(HIGHLIGHTS_STORE) const request = store.get(id) request.onsuccess = () => resolve(request.result || null) request.onerror = () => reject(new Error('Failed to get highlight')) }) } export async function getHighlightsByVerse(verseId: string): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly') const store = tx.objectStore(HIGHLIGHTS_STORE) const index = store.index('verseId') const request = index.getAll(verseId) request.onsuccess = () => resolve(request.result || []) request.onerror = () => reject(new Error('Failed to get highlights by verse')) }) } export async function getAllHighlights(): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly') const store = tx.objectStore(HIGHLIGHTS_STORE) const request = store.getAll() request.onsuccess = () => resolve(request.result || []) request.onerror = () => reject(new Error('Failed to get all highlights')) }) } export async function getPendingHighlights(): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readonly') const store = tx.objectStore(HIGHLIGHTS_STORE) const index = store.index('syncStatus') const request = index.getAll(IDBKeyRange.only('pending')) request.onsuccess = () => resolve(request.result || []) request.onerror = () => reject(new Error('Failed to get pending highlights')) }) } export async function deleteHighlight(id: string): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite') const store = tx.objectStore(HIGHLIGHTS_STORE) const request = store.delete(id) request.onsuccess = () => resolve() request.onerror = () => reject(new Error('Failed to delete highlight')) }) } export async function clearAllHighlights(): Promise { const db = await initHighlightsDatabase() return new Promise((resolve, reject) => { const tx = db.transaction(HIGHLIGHTS_STORE, 'readwrite') const store = tx.objectStore(HIGHLIGHTS_STORE) const request = store.clear() request.onsuccess = () => resolve() request.onerror = () => reject(new Error('Failed to clear highlights')) }) } ``` **Step 4: Run test to verify it passes** ```bash npm test -- __tests__/lib/highlight-manager.test.ts ``` Expected output: PASS - all 5 tests pass **Step 5: Commit** ```bash git add lib/highlight-manager.ts __tests__/lib/highlight-manager.test.ts git commit -m "feat: create highlight manager with IndexedDB storage" ``` --- ## Task 3: Create Highlight Sync Manager Service **Files:** - Create: `/root/biblical-guide/lib/highlight-sync-manager.ts` - Test: `/root/biblical-guide/__tests__/lib/highlight-sync-manager.test.ts` (create) **Step 1: Write failing test** Create file `/root/biblical-guide/__tests__/lib/highlight-sync-manager.test.ts`: ```typescript import { HighlightSyncManager } from '@/lib/highlight-sync-manager' import { BibleHighlight } from '@/types' describe('HighlightSyncManager', () => { let manager: HighlightSyncManager beforeEach(() => { manager = new HighlightSyncManager() }) it('should add highlight to sync queue', async () => { const highlight: BibleHighlight = { id: 'h-1', verseId: 'v-1', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } await manager.queueHighlight(highlight) const pending = await manager.getPendingSyncItems() expect(pending.length).toBe(1) expect(pending[0].highlightId).toBe('h-1') }) it('should mark highlight as syncing', async () => { const highlight: BibleHighlight = { id: 'h-1', verseId: 'v-1', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } await manager.queueHighlight(highlight) await manager.markSyncing(['h-1']) const syncing = await manager.getSyncingItems() expect(syncing.length).toBe(1) }) it('should mark highlight as synced', async () => { const highlight: BibleHighlight = { id: 'h-1', verseId: 'v-1', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } await manager.queueHighlight(highlight) await manager.markSynced(['h-1']) const pending = await manager.getPendingSyncItems() expect(pending.length).toBe(0) }) it('should retry sync on error', async () => { const highlight: BibleHighlight = { id: 'h-1', verseId: 'v-1', color: 'yellow', createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } await manager.queueHighlight(highlight) await manager.markError(['h-1'], 'Network error') await manager.markSyncing(['h-1']) const syncing = await manager.getSyncingItems() expect(syncing.length).toBe(1) }) }) ``` **Step 2: Run test to verify it fails** ```bash npm test -- __tests__/lib/highlight-sync-manager.test.ts ``` Expected output: FAIL - "HighlightSyncManager is not defined" **Step 3: Create highlight-sync-manager.ts** Create file `/root/biblical-guide/lib/highlight-sync-manager.ts`: ```typescript import { BibleHighlight, HighlightSyncQueueItem } from '@/types' import { initHighlightsDatabase, updateHighlight, getHighlight } from './highlight-manager' const SYNC_QUEUE_STORE = 'highlight_sync_queue' export class HighlightSyncManager { private db: IDBDatabase | null = null private syncInterval: NodeJS.Timeout | null = null async init() { this.db = await initHighlightsDatabase() // Create sync queue store if it doesn't exist if (!this.db.objectStoreNames.contains(SYNC_QUEUE_STORE)) { // Note: In real app, this would be done in onupgradeneeded // For this implementation, assume schema is managed separately } } async queueHighlight(highlight: BibleHighlight): Promise { if (!this.db) await this.init() const queueItem: HighlightSyncQueueItem = { highlightId: highlight.id, action: highlight.syncStatus === 'synced' ? 'update' : 'create', highlight, retryCount: 0 } await updateHighlight({ ...highlight, syncStatus: 'pending' }) } async getPendingSyncItems(): Promise { if (!this.db) await this.init() return new Promise((resolve, reject) => { const tx = this.db!.transaction('highlights', 'readonly') const store = tx.objectStore('highlights') const index = store.index('syncStatus') const request = index.getAll('pending') request.onsuccess = () => resolve(request.result || []) request.onerror = () => reject(new Error('Failed to get pending items')) }) } async getSyncingItems(): Promise { if (!this.db) await this.init() return new Promise((resolve, reject) => { const tx = this.db!.transaction('highlights', 'readonly') const store = tx.objectStore('highlights') const index = store.index('syncStatus') const request = index.getAll('syncing') request.onsuccess = () => resolve(request.result || []) request.onerror = () => reject(new Error('Failed to get syncing items')) }) } async markSyncing(highlightIds: string[]): Promise { if (!this.db) await this.init() for (const id of highlightIds) { const highlight = await getHighlight(id) if (highlight) { await updateHighlight({ ...highlight, syncStatus: 'syncing' }) } } } async markSynced(highlightIds: string[]): Promise { if (!this.db) await this.init() for (const id of highlightIds) { const highlight = await getHighlight(id) if (highlight) { await updateHighlight({ ...highlight, syncStatus: 'synced' }) } } } async markError(highlightIds: string[], errorMsg: string): Promise { if (!this.db) await this.init() for (const id of highlightIds) { const highlight = await getHighlight(id) if (highlight) { await updateHighlight({ ...highlight, syncStatus: 'error', syncErrorMsg: errorMsg }) } } } startAutoSync(intervalMs: number = 30000, onSyncNeeded?: () => void) { this.syncInterval = setInterval(async () => { const pending = await this.getPendingSyncItems() if (pending.length > 0 && onSyncNeeded) { onSyncNeeded() } }, intervalMs) } stopAutoSync() { if (this.syncInterval) { clearInterval(this.syncInterval) this.syncInterval = null } } } ``` **Step 4: Run test to verify it passes** ```bash npm test -- __tests__/lib/highlight-sync-manager.test.ts ``` Expected output: PASS - all 5 tests pass **Step 5: Commit** ```bash git add lib/highlight-sync-manager.ts __tests__/lib/highlight-sync-manager.test.ts git commit -m "feat: create highlight sync manager with queue logic" ``` --- ## Task 4: Create HighlightsTab Component **Files:** - Create: `/root/biblical-guide/components/bible/highlights-tab.tsx` - Modify: `/root/biblical-guide/components/bible/verse-details-panel.tsx` - Test: `/root/biblical-guide/__tests__/components/highlights-tab.test.tsx` (create) **Step 1: Write failing test** Create file `/root/biblical-guide/__tests__/components/highlights-tab.test.tsx`: ```typescript import { render, screen, fireEvent } from '@testing-library/react' import { HighlightsTab } from '@/components/bible/highlights-tab' import { BibleVerse } from '@/types' describe('HighlightsTab', () => { const mockVerse: BibleVerse = { id: 'v-1', verseNum: 1, text: 'In the beginning God created the heavens and the earth' } it('should render highlight button when verse not highlighted', () => { render( {}} onColorChange={() => {}} /> ) expect(screen.getByText(/Highlight/i)).toBeInTheDocument() }) it('should render color picker when verse is highlighted', () => { render( {}} onColorChange={() => {}} /> ) expect(screen.getByText(/Remove highlight/i)).toBeInTheDocument() }) it('should call onColorChange when color is selected', () => { const onColorChange = jest.fn() render( {}} onColorChange={onColorChange} /> ) const blueButton = screen.getByTestId('color-blue') fireEvent.click(blueButton) expect(onColorChange).toHaveBeenCalledWith('blue') }) }) ``` **Step 2: Run test to verify it fails** ```bash npm test -- __tests__/components/highlights-tab.test.tsx ``` Expected output: FAIL - "HighlightsTab is not defined" **Step 3: Create highlights-tab.tsx** Create file `/root/biblical-guide/components/bible/highlights-tab.tsx`: ```typescript 'use client' import { Box, Button, Grid, Typography, Divider } from '@mui/material' import { BibleVerse, HighlightColor } from '@/types' const HIGHLIGHT_COLORS: HighlightColor[] = ['yellow', 'orange', 'pink', 'blue'] const COLOR_MAP: Record = { yellow: { bg: 'rgba(255, 193, 7, 0.3)', hex: '#FFC107' }, orange: { bg: 'rgba(255, 152, 0, 0.3)', hex: '#FF9800' }, pink: { bg: 'rgba(233, 30, 99, 0.3)', hex: '#E91E63' }, blue: { bg: 'rgba(33, 150, 243, 0.3)', hex: '#2196F3' } } interface HighlightsTabProps { verse: BibleVerse | null isHighlighted: boolean currentColor: HighlightColor | null onToggleHighlight: () => void onColorChange: (color: HighlightColor) => void } export function HighlightsTab({ verse, isHighlighted, currentColor, onToggleHighlight, onColorChange }: HighlightsTabProps) { if (!verse) return null return ( {!isHighlighted ? ( ) : ( <> Highlight Color {HIGHLIGHT_COLORS.map((color) => ( ))} You can highlight the same verse multiple times with different colors. )} ) } ``` **Step 4: Run test to verify it passes** ```bash npm test -- __tests__/components/highlights-tab.test.tsx ``` Expected output: PASS - all 3 tests pass **Step 5: Modify VersDetailsPanel to include HighlightsTab** In `/root/biblical-guide/components/bible/verse-details-panel.tsx`, import and add the HighlightsTab: ```typescript import { HighlightsTab } from './highlights-tab' // In the TabsContainer, add: // In the TabPanel area, add: {}} // TODO: implement onColorChange={() => {}} // TODO: implement /> ``` **Step 6: Commit** ```bash git add components/bible/highlights-tab.tsx __tests__/components/highlights-tab.test.tsx git commit -m "feat: create HighlightsTab component with color picker" ``` --- ## Task 5: Enhance VerseRenderer with Highlight Background **Files:** - Modify: `/root/biblical-guide/components/bible/reading-view.tsx` - Test: (add to existing reading-view tests) **Step 1: Update VerseRenderer to show highlight** In `/root/biblical-guide/components/bible/reading-view.tsx`, find the verse rendering code and update: ```typescript // Add highlight background color support const COLOR_MAP: Record = { yellow: 'rgba(255, 193, 7, 0.3)', orange: 'rgba(255, 152, 0, 0.3)', pink: 'rgba(233, 30, 99, 0.3)', blue: 'rgba(33, 150, 243, 0.3)' } // In the verse rendering, update the span to: { e.stopPropagation() onVerseClick(verse.id) }} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() onVerseClick(verse.id) } }} onMouseEnter={() => setHoveredVerseNum(verse.verseNum)} onMouseLeave={() => setHoveredVerseNum(null)} style={{ backgroundColor: verse.highlight ? COLOR_MAP[verse.highlight.color] : 'transparent', padding: '0.25rem 0.5rem', borderRadius: '4px', cursor: 'pointer', transition: 'all 0.2s ease' }} > {verse.text} ``` Note: This assumes `verse` has been extended with an optional `highlight` property. For now, this will be a visual placeholder for Task 6 integration. **Step 2: Commit** ```bash git add components/bible/reading-view.tsx git commit -m "feat: add highlight background color support to verse renderer" ``` --- ## Task 6: Integrate Highlights into BibleReaderApp **Files:** - Modify: `/root/biblical-guide/components/bible/bible-reader-app.tsx` - Modify: `/root/biblical-guide/components/bible/verse-details-panel.tsx` **Step 1: Update BibleReaderApp to manage highlights** Add highlight state and sync manager to `/root/biblical-guide/components/bible/bible-reader-app.tsx`: ```typescript import { HighlightSyncManager } from '@/lib/highlight-sync-manager' import { addHighlight, updateHighlight, getHighlightsByVerse, deleteHighlight } from '@/lib/highlight-manager' import { BibleHighlight, HighlightColor } from '@/types' // In component state: const [highlights, setHighlights] = useState>(new Map()) const syncManager = useRef(null) // Initialize sync manager on mount: useEffect(() => { syncManager.current = new HighlightSyncManager() syncManager.current.init() syncManager.current.startAutoSync(30000, () => { performSync() }) return () => { syncManager.current?.stopAutoSync() } }, []) // Load all highlights on mount: useEffect(() => { loadAllHighlights() }, []) // Functions to add to component: async function loadAllHighlights() { try { const highlightList = await getAllHighlights() const map = new Map(highlightList.map(h => [h.verseId, h])) setHighlights(map) } catch (error) { console.error('Failed to load highlights:', error) } } async function handleHighlightVerse(color: HighlightColor = 'yellow') { if (!selectedVerse) return const highlight: BibleHighlight = { id: `h-${selectedVerse.id}-${Date.now()}`, verseId: selectedVerse.id, color, createdAt: Date.now(), updatedAt: Date.now(), syncStatus: 'pending' } try { await addHighlight(highlight) const newMap = new Map(highlights) newMap.set(selectedVerse.id, highlight) setHighlights(newMap) } catch (error) { console.error('Failed to highlight verse:', error) } } async function handleChangeHighlightColor(color: HighlightColor) { if (!selectedVerse) return const existing = highlights.get(selectedVerse.id) if (existing) { const updated = { ...existing, color, updatedAt: Date.now(), syncStatus: 'pending' as const } try { await updateHighlight(updated) const newMap = new Map(highlights) newMap.set(selectedVerse.id, updated) setHighlights(newMap) } catch (error) { console.error('Failed to update highlight color:', error) } } } async function handleRemoveHighlight() { if (!selectedVerse) return try { await deleteHighlight(`h-${selectedVerse.id}-*`) const newMap = new Map(highlights) newMap.delete(selectedVerse.id) setHighlights(newMap) } catch (error) { console.error('Failed to remove highlight:', error) } } async function performSync() { if (!syncManager.current) return try { const pending = await syncManager.current.getPendingSyncItems() if (pending.length === 0) return await syncManager.current.markSyncing(pending.map(h => h.id)) // TODO: POST to /api/highlights/bulk in Phase 2.1B await syncManager.current.markSynced(pending.map(h => h.id)) } catch (error) { console.error('Sync failed:', error) // Mark items with error status } } ``` **Step 2: Update VersDetailsPanel to use highlight functions** In `/root/biblical-guide/components/bible/verse-details-panel.tsx`: ```typescript interface VersDetailsPanelProps { // ... existing props isHighlighted?: boolean currentHighlightColor?: HighlightColor | null onHighlightVerse?: (color: HighlightColor) => void onChangeHighlightColor?: (color: HighlightColor) => void onRemoveHighlight?: () => void } // Pass to HighlightsTab: { if (isHighlighted) { onRemoveHighlight?.() } else { onHighlightVerse?.('yellow') } }} onColorChange={(color) => onChangeHighlightColor?.(color)} /> ``` **Step 3: Pass highlight state from BibleReaderApp to VersDetailsPanel** ```typescript ``` **Step 4: Commit** ```bash git add components/bible/bible-reader-app.tsx components/bible/verse-details-panel.tsx git commit -m "feat: integrate highlight management into reader app" ``` --- ## Task 7: Backend API Endpoints for Highlights **Files:** - Create: `/root/biblical-guide/app/api/highlights/route.ts` - Create: `/root/biblical-guide/app/api/highlights/bulk/route.ts` - Create: `/root/biblical-guide/app/api/highlights/all/route.ts` - Create: `/root/biblical-guide/app/api/bible/cross-references/route.ts` **Step 1: Create POST /api/highlights** Create `/root/biblical-guide/app/api/highlights/route.ts`: ```typescript import { NextResponse } from 'next/server' import { prisma } from '@/lib/db' import { getAuth } from '@clerk/nextjs/server' export const runtime = 'nodejs' export async function POST(request: Request) { try { const { userId } = await getAuth(request) if (!userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const body = await request.json() const { verseId, color } = body if (!verseId || !['yellow', 'orange', 'pink', 'blue'].includes(color)) { return NextResponse.json({ error: 'Invalid input' }, { status: 400 }) } const highlight = await prisma.userHighlight.create({ data: { userId, verseId, color, createdAt: new Date(), updatedAt: new Date() } }) return NextResponse.json({ id: highlight.id, verseId: highlight.verseId, color: highlight.color, createdAt: highlight.createdAt.getTime(), updatedAt: highlight.updatedAt.getTime(), syncStatus: 'synced' }) } catch (error) { console.error('Error creating highlight:', error) return NextResponse.json( { error: 'Failed to create highlight' }, { status: 500 } ) } } ``` **Step 2: Create POST /api/highlights/bulk** Create `/root/biblical-guide/app/api/highlights/bulk/route.ts`: ```typescript import { NextResponse } from 'next/server' import { prisma } from '@/lib/db' import { getAuth } from '@clerk/nextjs/server' export const runtime = 'nodejs' export async function POST(request: Request) { try { const { userId } = await getAuth(request) if (!userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const body = await request.json() const { highlights } = body if (!Array.isArray(highlights)) { return NextResponse.json({ error: 'Invalid input' }, { status: 400 }) } const synced = [] const errors = [] for (const item of highlights) { try { const existing = await prisma.userHighlight.findFirst({ where: { userId, verseId: item.verseId } }) if (existing) { await prisma.userHighlight.update({ where: { id: existing.id }, data: { color: item.color, updatedAt: new Date() } }) } else { await prisma.userHighlight.create({ data: { userId, verseId: item.verseId, color: item.color, createdAt: new Date(), updatedAt: new Date() } }) } synced.push(item.verseId) } catch (e) { errors.push({ verseId: item.verseId, error: 'Failed to sync' }) } } return NextResponse.json({ synced: synced.length, errors, serverTime: Date.now() }) } catch (error) { console.error('Error bulk syncing highlights:', error) return NextResponse.json( { error: 'Failed to sync highlights' }, { status: 500 } ) } } ``` **Step 3: Create GET /api/highlights/all** Create `/root/biblical-guide/app/api/highlights/all/route.ts`: ```typescript import { NextResponse } from 'next/server' import { prisma } from '@/lib/db' import { getAuth } from '@clerk/nextjs/server' export const runtime = 'nodejs' export async function GET(request: Request) { try { const { userId } = await getAuth(request) if (!userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const highlights = await prisma.userHighlight.findMany({ where: { userId }, select: { id: true, verseId: true, color: true, createdAt: true, updatedAt: true } }) return NextResponse.json({ highlights: highlights.map(h => ({ id: h.id, verseId: h.verseId, color: h.color, createdAt: h.createdAt.getTime(), updatedAt: h.updatedAt.getTime() })), serverTime: Date.now() }) } catch (error) { console.error('Error fetching highlights:', error) return NextResponse.json( { error: 'Failed to fetch highlights' }, { status: 500 } ) } } ``` **Step 4: Create GET /api/bible/cross-references** Create `/root/biblical-guide/app/api/bible/cross-references/route.ts`: ```typescript import { NextResponse } from 'next/server' import { prisma } from '@/lib/db' export const runtime = 'nodejs' export async function GET(request: Request) { try { const { searchParams } = new URL(request.url) const verseId = searchParams.get('verseId') if (!verseId) { return NextResponse.json( { error: 'verseId parameter required' }, { status: 400 } ) } // For now, return empty cross-references // TODO: Implement actual cross-reference lookup in Phase 2.1B // This would require a cross_references table mapping verses to related verses return NextResponse.json({ verseId, references: [] }) } catch (error) { console.error('Error fetching cross-references:', error) return NextResponse.json( { error: 'Failed to fetch cross-references' }, { status: 500 } ) } } ``` **Step 5: Commit** ```bash git add app/api/highlights/ app/api/bible/cross-references/route.ts git commit -m "feat: add backend API endpoints for highlights and cross-references" ``` --- ## Task 8: Add Database Schema for Highlights **Files:** - Create/Modify: `prisma/schema.prisma` **Step 1: Add UserHighlight model to schema** Add to `/root/biblical-guide/prisma/schema.prisma`: ```prisma model UserHighlight { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) verseId String color String @default("yellow") // yellow, orange, pink, blue createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([userId, verseId]) @@index([userId]) @@index([verseId]) } ``` Also add relation to User model: ```prisma model User { // ... existing fields highlights UserHighlight[] } ``` **Step 2: Create and run migration** ```bash npx prisma migrate dev --name add_highlights ``` Expected output: Migration created and applied successfully **Step 3: Commit** ```bash git add prisma/schema.prisma prisma/migrations/ git commit -m "feat: add UserHighlight model to database schema" ``` --- ## Task 9: Update Imports and Build Verification **Files:** - Modify: `/root/biblical-guide/lib/highlight-manager.ts` - add getAllHighlights export if missing - Run full build **Step 1: Verify all imports are correct** Ensure the following are exported from each module: From `/root/biblical-guide/lib/highlight-manager.ts`: ```typescript export { initHighlightsDatabase, addHighlight, updateHighlight, getHighlight, getHighlightsByVerse, getAllHighlights, getPendingHighlights, deleteHighlight, clearAllHighlights } ``` **Step 2: Run build** ```bash npm run build 2>&1 | tail -50 ``` Expected output: Build completed successfully with no type errors **Step 3: Commit** ```bash git add -A git commit -m "build: complete Phase 2.1 implementation and verify build" ``` --- ## Summary of Implementation This plan implements: ✅ **Types** - TypeScript definitions for highlights, colors, sync status ✅ **Storage** - IndexedDB-based storage with sync queue ✅ **Sync Manager** - Background sync service with queue management ✅ **UI Components** - HighlightsTab for color selection and management ✅ **Reader Integration** - Highlight state management in BibleReaderApp ✅ **Visual Feedback** - Colored backgrounds on highlighted verses ✅ **Backend APIs** - Four new endpoints for highlight CRUD and cross-references ✅ **Database** - UserHighlight model with proper relations ✅ **Testing** - Unit tests for all components and services **Total effort**: ~8-10 hours for experienced developer with zero context **Next Phase (2.1B):** - Implement actual sync POST calls in performSync() - Add cross-device highlight merge logic - Implement cross-references lookup in backend - Add sync status indicator UI - Full E2E testing ---