Includes all Phase 1 features: - Search-first navigation with auto-complete - Responsive reading interface (desktop/tablet/mobile) - 4 customization presets + full fine-tuning controls - Layered details panel with notes, bookmarks, highlights - Smart offline caching with IndexedDB and auto-sync - Full accessibility (WCAG 2.1 AA) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
24 KiB
24 KiB
Cross-References Panel - Implementation Plan
📋 Overview
Implement a comprehensive cross-reference system that helps users discover related Scripture passages, understand context, trace themes, and build a deeper knowledge of interconnected Bible teachings.
Status: Planning Phase Priority: 🔴 High Estimated Time: 2 weeks (80 hours) Target Completion: TBD
🎯 Goals & Objectives
Primary Goals
- Display relevant cross-references for any verse
- Provide context and categorization for references
- Enable quick navigation between related passages
- Support custom user-added cross-references
- Visualize reference networks and themes
User Value Proposition
- For Bible students: Understand context and connections
- For teachers: Prepare comprehensive lessons
- For scholars: Research thematic progressions
- For new readers: Discover related teachings
- For memorizers: Build mental maps of Scripture
✨ Feature Specifications
1. Cross-Reference Data Model
interface CrossReference {
id: string
fromVerse: VerseReference
toVerse: VerseReference
type: ReferenceType
category: string
strength: number // 0-100, relevance score
direction: 'forward' | 'backward' | 'bidirectional'
source: 'openbible' | 'user' | 'treasury' | 'commentaries'
description?: string
addedBy?: string // User ID for custom references
votes?: number // Community voting on quality
createdAt: Date
}
interface VerseReference {
book: string
chapter: number
verse: number
endVerse?: number // For ranges
}
type ReferenceType =
| 'quotation' // Direct quote (OT → NT)
| 'allusion' // Indirect reference
| 'parallel' // Parallel account (Gospels, Kings/Chronicles)
| 'thematic' // Same theme/topic
| 'fulfillment' // Prophecy fulfillment
| 'contrast' // Contrasting teaching
| 'expansion' // Elaboration/explanation
| 'application' // Practical application
| 'historical' // Historical context
| 'wordStudy' // Same Hebrew/Greek word
2. Cross-Reference Categories
const REFERENCE_CATEGORIES = {
// Structural
'parallel-passages': 'Parallel Passages',
'quotations': 'Quotations',
'allusions': 'Allusions',
// Thematic
'salvation': 'Salvation',
'faith': 'Faith',
'love': 'Love',
'judgment': 'Judgment',
'prophecy': 'Prophecy',
'miracles': 'Miracles',
'parables': 'Parables',
'promises': 'Promises',
'commands': 'Commands',
'covenants': 'Covenants',
// Character Studies
'christ-prefigured': 'Christ Prefigured',
'messianic': 'Messianic References',
'holy-spirit': 'Holy Spirit',
// Literary
'poetry': 'Poetic Parallels',
'wisdom': 'Wisdom Literature',
'apocalyptic': 'Apocalyptic Literature',
// Historical
'chronological': 'Chronological Sequence',
'geographical': 'Same Location',
// Custom
'user-defined': 'User Added'
}
3. UI Layout Options
Desktop - Sidebar (Default):
┌────────────────────────────┬──────────────────┐
│ Genesis 1:1-31 │ Cross-References │
│ │ │
│ 1 In the beginning God │ ▸ Quotations (3) │
│ created the heaven and │ • John 1:1-3 │
│ the earth. │ • Heb 11:3 │
│ │ • Rev 4:11 │
│ 2 And the earth was │ │
│ without form... │ ▸ Parallel (2) │
│ │ • Ps 33:6 │
│ │ • Col 1:16 │
│ │ │
│ [verse 3 selected] │ ▸ Thematic (12) │
│ 3 And God said, Let │ • Gen 2:3 │
│ there be light: and │ • 2 Cor 4:6 │
│ there was light. │ • Jas 1:17 │
│ │ + 9 more │
└────────────────────────────┴──────────────────┘
Mobile - Bottom Sheet:
┌─────────────────────────┐
│ Genesis 1:3 │
│ │
│ And God said, Let there │
│ be light: and there was │
│ light. │
│ │
│ [Tap for references] ▲ │
└─────────────────────────┘
↓ Swipe up
┌─────────────────────────┐
│ ≡ Cross-References (17) │
├─────────────────────────┤
│ Quotations (3) │
│ • John 1:1-3 → │
│ • Hebrews 11:3 → │
│ │
│ Thematic (12) │
│ • Genesis 2:3 → │
│ • 2 Cor 4:6 → │
│ + 10 more │
└─────────────────────────┘
4. Collapsible Sidebar Component
interface CrossReferencePanelProps {
verse: VerseReference | null
position: 'left' | 'right' | 'bottom'
defaultOpen: boolean
width: number // pixels or percentage
}
export const CrossReferencePanel: React.FC<CrossReferencePanelProps> = ({
verse,
position = 'right',
defaultOpen = true,
width = 320
}) => {
const [isOpen, setIsOpen] = useState(defaultOpen)
const [references, setReferences] = useState<CrossReference[]>([])
const [loading, setLoading] = useState(false)
const [groupBy, setGroupBy] = useState<'type' | 'category'>('type')
const [sortBy, setSortBy] = useState<'relevance' | 'book' | 'votes'>('relevance')
useEffect(() => {
if (!verse) {
setReferences([])
return
}
loadReferences(verse)
}, [verse])
const loadReferences = async (verse: VerseReference) => {
setLoading(true)
try {
const response = await fetch(
`/api/cross-references?book=${verse.book}&chapter=${verse.chapter}&verse=${verse.verse}`
)
const data = await response.json()
setReferences(data.references)
} catch (error) {
console.error('Failed to load cross-references:', error)
} finally {
setLoading(false)
}
}
const groupedReferences = useMemo(() => {
if (groupBy === 'type') {
return groupByType(references)
} else {
return groupByCategory(references)
}
}, [references, groupBy])
const sortedGroups = useMemo(() => {
return Object.entries(groupedReferences).map(([key, refs]) => ({
key,
references: sortReferences(refs, sortBy)
}))
}, [groupedReferences, sortBy])
if (!verse) return null
return (
<Drawer
anchor={position}
open={isOpen}
variant="persistent"
sx={{
width: isOpen ? width : 0,
flexShrink: 0,
'& .MuiDrawer-paper': {
width,
boxSizing: 'border-box',
top: 64, // Below header
height: 'calc(100% - 64px)'
}
}}
>
{/* Header */}
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">
Cross-References
</Typography>
<IconButton size="small" onClick={() => setIsOpen(false)}>
<CloseIcon />
</IconButton>
</Box>
<Typography variant="caption" color="text.secondary">
{verse.book} {verse.chapter}:{verse.verse}
</Typography>
</Box>
{/* Controls */}
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Box display="flex" gap={1} mb={1}>
<FormControl size="small" fullWidth>
<InputLabel>Group By</InputLabel>
<Select
value={groupBy}
onChange={(e) => setGroupBy(e.target.value as any)}
label="Group By"
>
<MenuItem value="type">Type</MenuItem>
<MenuItem value="category">Category</MenuItem>
</Select>
</FormControl>
<FormControl size="small" fullWidth>
<InputLabel>Sort By</InputLabel>
<Select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)}
label="Sort By"
>
<MenuItem value="relevance">Relevance</MenuItem>
<MenuItem value="book">Book Order</MenuItem>
<MenuItem value="votes">Most Voted</MenuItem>
</Select>
</FormControl>
</Box>
<Box display="flex" gap={1}>
<Button size="small" variant="outlined" fullWidth>
<AddIcon /> Add Reference
</Button>
<IconButton size="small">
<FilterListIcon />
</IconButton>
</Box>
</Box>
{/* References List */}
<Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
{loading ? (
<Box display="flex" justifyContent="center" p={3}>
<CircularProgress />
</Box>
) : references.length === 0 ? (
<Alert severity="info">
No cross-references found for this verse.
</Alert>
) : (
sortedGroups.map(group => (
<ReferenceGroup
key={group.key}
title={group.key}
references={group.references}
/>
))
)}
</Box>
</Drawer>
)
}
5. Reference Group Component
interface ReferenceGroupProps {
title: string
references: CrossReference[]
defaultExpanded?: boolean
}
const ReferenceGroup: React.FC<ReferenceGroupProps> = ({
title,
references,
defaultExpanded = true
}) => {
const [expanded, setExpanded] = useState(defaultExpanded)
const [previewVerse, setPreviewVerse] = useState<string | null>(null)
return (
<Box mb={2}>
<Box
onClick={() => setExpanded(!expanded)}
sx={{
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
p: 1,
borderRadius: 1,
'&:hover': { bgcolor: 'action.hover' }
}}
>
<IconButton size="small">
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
<Typography variant="subtitle2" fontWeight="600">
{title} ({references.length})
</Typography>
</Box>
<Collapse in={expanded}>
<List dense>
{references.map(ref => (
<ReferenceItem
key={ref.id}
reference={ref}
onHover={setPreviewVerse}
/>
))}
</List>
</Collapse>
{/* Preview popover */}
{previewVerse && (
<VersePreviewPopover
verseText={previewVerse}
onClose={() => setPreviewVerse(null)}
/>
)}
</Box>
)
}
6. Reference Item with Preview
interface ReferenceItemProps {
reference: CrossReference
onHover: (verseText: string | null) => void
}
const ReferenceItem: React.FC<ReferenceItemProps> = ({
reference,
onHover
}) => {
const router = useRouter()
const [verseText, setVerseText] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const handleMouseEnter = async () => {
if (verseText) {
onHover(verseText)
return
}
setLoading(true)
try {
const response = await fetch(
`/api/bible/verses?` +
`book=${reference.toVerse.book}&` +
`chapter=${reference.toVerse.chapter}&` +
`verse=${reference.toVerse.verse}`
)
const data = await response.json()
const text = data.verses[0]?.text || ''
setVerseText(text)
onHover(text)
} catch (error) {
console.error('Failed to load verse preview:', error)
} finally {
setLoading(false)
}
}
const handleClick = () => {
const { book, chapter, verse } = reference.toVerse
router.push(`/bible/${book.toLowerCase()}/${chapter}#verse-${verse}`)
}
const formatReference = (ref: VerseReference): string => {
const baseRef = `${ref.book} ${ref.chapter}:${ref.verse}`
return ref.endVerse ? `${baseRef}-${ref.endVerse}` : baseRef
}
const getTypeIcon = (type: ReferenceType) => {
const icons = {
quotation: <FormatQuoteIcon fontSize="small" />,
parallel: <CompareArrowsIcon fontSize="small" />,
thematic: <CategoryIcon fontSize="small" />,
fulfillment: <CheckCircleIcon fontSize="small" />,
allusion: <LinkIcon fontSize="small" />,
// ... more mappings
}
return icons[type] || <ArticleIcon fontSize="small" />
}
return (
<ListItem
button
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={() => onHover(null)}
sx={{
borderRadius: 1,
mb: 0.5,
'&:hover': {
bgcolor: 'action.hover'
}
}}
>
<ListItemIcon sx={{ minWidth: 36 }}>
{getTypeIcon(reference.type)}
</ListItemIcon>
<ListItemText
primary={
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="body2" fontWeight="500">
{formatReference(reference.toVerse)}
</Typography>
{reference.strength >= 80 && (
<Chip label="High" size="small" color="success" />
)}
</Box>
}
secondary={reference.description}
/>
<ListItemSecondaryAction>
<IconButton size="small" edge="end">
<ArrowForwardIcon fontSize="small" />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)
}
7. Visual Indicators in Text
// Add superscript indicators in verse text
const VerseWithReferences: React.FC<{
verse: BibleVerse
references: CrossReference[]
}> = ({ verse, references }) => {
const hasReferences = references.length > 0
return (
<Box
className="verse"
data-verse={verse.verseNum}
sx={{ position: 'relative' }}
>
<Typography
component="span"
className="verse-number"
sx={{ mr: 1, fontWeight: 600, color: 'text.secondary' }}
>
{verse.verseNum}
</Typography>
<Typography component="span" className="verse-text">
{verse.text}
</Typography>
{hasReferences && (
<Tooltip title={`${references.length} cross-references`}>
<IconButton
size="small"
sx={{
ml: 0.5,
width: 20,
height: 20,
fontSize: '0.75rem'
}}
>
<Badge badgeContent={references.length} color="primary">
<LinkIcon fontSize="inherit" />
</Badge>
</IconButton>
</Tooltip>
)}
</Box>
)
}
8. Add Custom Cross-Reference
interface AddReferenceDialogProps {
open: boolean
onClose: () => void
fromVerse: VerseReference
}
const AddReferenceDialog: React.FC<AddReferenceDialogProps> = ({
open,
onClose,
fromVerse
}) => {
const [toVerse, setToVerse] = useState<VerseReference | null>(null)
const [type, setType] = useState<ReferenceType>('thematic')
const [category, setCategory] = useState('')
const [description, setDescription] = useState('')
const handleSubmit = async () => {
if (!toVerse) return
try {
await fetch('/api/cross-references', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fromVerse,
toVerse,
type,
category,
description,
source: 'user'
})
})
onClose()
} catch (error) {
console.error('Failed to add cross-reference:', error)
}
}
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>Add Cross-Reference</DialogTitle>
<DialogContent>
<Box sx={{ pt: 2, display: 'flex', flexDirection: 'column', gap: 2 }}>
<Typography variant="body2" color="text.secondary">
From: {fromVerse.book} {fromVerse.chapter}:{fromVerse.verse}
</Typography>
<VerseSelector
label="To Verse"
value={toVerse}
onChange={setToVerse}
/>
<FormControl fullWidth>
<InputLabel>Type</InputLabel>
<Select value={type} onChange={(e) => setType(e.target.value as ReferenceType)}>
<MenuItem value="quotation">Quotation</MenuItem>
<MenuItem value="parallel">Parallel</MenuItem>
<MenuItem value="thematic">Thematic</MenuItem>
<MenuItem value="allusion">Allusion</MenuItem>
<MenuItem value="fulfillment">Fulfillment</MenuItem>
</Select>
</FormControl>
<TextField
label="Category"
value={category}
onChange={(e) => setCategory(e.target.value)}
fullWidth
/>
<TextField
label="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
multiline
rows={3}
fullWidth
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={handleSubmit} variant="contained">
Add Reference
</Button>
</DialogActions>
</Dialog>
)
}
9. Bidirectional Linking
// Automatically create reverse references
const createBidirectionalReference = async (
fromVerse: VerseReference,
toVerse: VerseReference,
type: ReferenceType
) => {
// Create forward reference
await createReference({
fromVerse,
toVerse,
type,
direction: 'forward'
})
// Create backward reference automatically
await createReference({
fromVerse: toVerse,
toVerse: fromVerse,
type,
direction: 'backward'
})
}
10. Search Cross-References
interface ReferenceSearchProps {
onSelect: (reference: CrossReference) => void
}
const ReferenceSearch: React.FC<ReferenceSearchProps> = ({ onSelect }) => {
const [query, setQuery] = useState('')
const [results, setResults] = useState<CrossReference[]>([])
const handleSearch = useDebounce(async (searchQuery: string) => {
if (searchQuery.length < 3) {
setResults([])
return
}
const response = await fetch(
`/api/cross-references/search?q=${encodeURIComponent(searchQuery)}`
)
const data = await response.json()
setResults(data.references)
}, 300)
return (
<Box>
<TextField
placeholder="Search references by verse, theme, or keyword..."
value={query}
onChange={(e) => {
setQuery(e.target.value)
handleSearch(e.target.value)
}}
fullWidth
InputProps={{
startAdornment: <SearchIcon />
}}
/>
<List>
{results.map(ref => (
<ListItem
key={ref.id}
button
onClick={() => onSelect(ref)}
>
<ListItemText
primary={formatReference(ref.fromVerse)}
secondary={ref.description}
/>
</ListItem>
))}
</List>
</Box>
)
}
🗄️ Database Schema
model CrossReference {
id String @id @default(cuid())
// From verse
fromBook String
fromChapter Int
fromVerse Int
fromEndVerse Int?
// To verse
toBook String
toChapter Int
toVerse Int
toEndVerse Int?
// Metadata
type String // ReferenceType enum
category String?
strength Int @default(50) // 0-100
direction String @default("bidirectional")
source String @default("openbible") // openbible, user, treasury
description String?
// User tracking (for custom references)
addedBy String?
userId String?
user User? @relation(fields: [userId], references: [id])
// Community features
votes Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([fromBook, fromChapter, fromVerse])
@@index([toBook, toChapter, toVerse])
@@index([type, category])
@@index([userId])
}
model ReferenceVote {
id String @id @default(cuid())
referenceId String
userId String
value Int // +1 or -1
reference CrossReference @relation(fields: [referenceId], references: [id])
user User @relation(fields: [userId], references: [id])
@@unique([referenceId, userId])
@@index([referenceId])
}
📊 API Endpoints
// Get cross-references for a verse
GET /api/cross-references
Query params:
- book: string
- chapter: number
- verse: number
- type?: ReferenceType[]
- category?: string[]
- minStrength?: number (0-100)
Response: {
references: CrossReference[]
count: number
}
// Add custom cross-reference
POST /api/cross-references
Body: {
fromVerse: VerseReference
toVerse: VerseReference
type: ReferenceType
category?: string
description?: string
}
Response: {
success: boolean
reference: CrossReference
}
// Vote on reference quality
POST /api/cross-references/:id/vote
Body: { value: 1 | -1 }
// Search cross-references
GET /api/cross-references/search
Query: q=keyword
Response: { references: CrossReference[] }
// Bulk import cross-references (admin)
POST /api/admin/cross-references/import
Body: { references: CrossReference[], source: string }
📅 Implementation Timeline
Week 1: Foundation & Data
Day 1-2: Database & Data Import
- Create database schema
- Import OpenBible.info dataset (~65,000 references)
- Build API endpoints
- Test data queries
Day 3-4: UI Components
- Create sidebar component
- Build reference list UI
- Implement grouping/sorting
- Add loading states
Day 5: Navigation & Preview
- Implement click navigation
- Build hover preview
- Add verse indicators
- Test UX flow
Deliverable: Working cross-reference viewer
Week 2: Advanced Features
Day 1-2: Custom References
- Build add reference dialog
- Implement bidirectional linking
- Add edit/delete functionality
- Test CRUD operations
Day 3-4: Search & Filter
- Implement search
- Add advanced filters
- Build category browser
- Add sorting options
Day 5: Polish & Mobile
- Optimize mobile layout
- Performance tuning
- Bug fixes
- Documentation
Deliverable: Production-ready cross-reference system
📚 Data Sources
OpenBible.info Cross-Reference Dataset
- URL: https://openbible.info/labs/cross-references/
- Size: ~340,000 cross-references
- License: CC BY 4.0
- Coverage: Old & New Testament
- Format: CSV/JSON
Treasury of Scripture Knowledge
- Coverage: Extensive OT/NT references
- Public Domain: Yes
- Quality: High (curated by scholars)
User-Generated References
- Allow community contributions
- Implement voting/quality system
- Moderate for accuracy
🚀 Deployment Plan
Pre-Launch
- Import cross-reference dataset
- Test with 1000+ verses
- Performance optimization
- Mobile testing
- Accessibility audit
Rollout
- Beta: 10% users, collect feedback
- Staged: 50% users
- Full: 100% deployment
Document Version: 1.0 Last Updated: 2025-10-13 Owner: Development Team Status: Ready for Implementation