Files
biblical-guide.com/CROSS_REFERENCES_PANEL_PLAN.md
Andrei 9b5c0ed8bb build: production build with Phase 1 2025 Bible Reader implementation complete
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>
2025-11-11 20:38:01 +00:00

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

  1. Display relevant cross-references for any verse
  2. Provide context and categorization for references
  3. Enable quick navigation between related passages
  4. Support custom user-added cross-references
  5. 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

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

  1. Beta: 10% users, collect feedback
  2. Staged: 50% users
  3. Full: 100% deployment

Document Version: 1.0 Last Updated: 2025-10-13 Owner: Development Team Status: Ready for Implementation