diff --git a/app/src/components/map/MapChipNative.tsx b/app/src/components/map/MapChipNative.tsx index cdb58c91b..7526ac714 100644 --- a/app/src/components/map/MapChipNative.tsx +++ b/app/src/components/map/MapChipNative.tsx @@ -26,8 +26,8 @@ import { STYLE_ANCIENT } from '../../constants/mapStyles'; import { useTheme, spacing, radii, fontFamily } from '../../theme'; import type { MapStory, Place } from '../../types'; import { safeParse } from '../../utils/logger'; -import { StoryOverlays } from './StoryOverlays'; import { ensureMapLibreInit } from '../../utils/isMapNativeAvailable'; +import { StoryOverlays } from './StoryOverlays'; // The MapChip dispatcher gates on isMapNativeAvailable() before loading // this module. Ensure the SDK is initialized before rendering. diff --git a/app/src/hooks/useTTS.ts b/app/src/hooks/useTTS.ts index 1777ea656..cd76cf3a4 100644 --- a/app/src/hooks/useTTS.ts +++ b/app/src/hooks/useTTS.ts @@ -56,7 +56,11 @@ export function useTTS(verses: Verse[], voiceId?: string) { * fully stable (no dependency array churn). The generation counter * ensures that if Speech.stop() fires a stale onDone after a new * utterance has already started, the stale callback is a no-op. + * + * Self-reference via speakVerseRef avoids the "accessed before declared" + * lint warning that a direct recursive useCallback would trigger. */ + const speakVerseRef = useRef<(index: number) => void>(() => {}); const speakVerse = useCallback((index: number) => { const vv = versesRef.current; if (index >= vv.length) { @@ -64,12 +68,9 @@ export function useTTS(verses: Verse[], voiceId?: string) { setCurrentVerse(0); return; } - // Bump generation so any in-flight onDone from a previous utterance - // sees a mismatch and does nothing. const gen = ++generation.current; stoppedManually.current = false; setCurrentVerse(index); - // Only announce the verse number at the start of an audio session const text = index === sessionStartIndex.current ? `Verse ${vv[index].verse_num}. ${vv[index].text}` : vv[index].text; @@ -80,12 +81,8 @@ export function useTTS(verses: Verse[], voiceId?: string) { pitch: 1.0, rate: speedRef.current, onDone: () => { - // Only auto-advance if this is still the active generation - // AND we didn't manually stop. Without the generation check, - // Speech.stop()'s async onDone can race with a new speakVerse - // call — causing repeats and out-of-order playback. if (generation.current === gen && !stoppedManually.current) { - speakVerse(index + 1); + speakVerseRef.current(index + 1); } }, onError: (err) => { @@ -93,7 +90,8 @@ export function useTTS(verses: Verse[], voiceId?: string) { setIsPlaying(false); }, }); - }, []); // Stable — all dependencies are refs + }, []); + useEffect(() => { speakVerseRef.current = speakVerse; }, [speakVerse]); const play = useCallback(async () => { if (versesRef.current.length === 0) { diff --git a/app/src/screens/MapScreenNative.tsx b/app/src/screens/MapScreenNative.tsx index 3cc212463..88a7fa7dc 100644 --- a/app/src/screens/MapScreenNative.tsx +++ b/app/src/screens/MapScreenNative.tsx @@ -15,10 +15,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { MapView, Camera, type CameraRef } from '@maplibre/maplibre-react-native'; import { usePlaces } from '../hooks/usePlaces'; import { ensureMapLibreInit } from '../utils/isMapNativeAvailable'; - -// The dispatcher (MapScreen.tsx) gates on isMapNativeAvailable() before -// this module loads. Run module-level init so tiles can be fetched. -ensureMapLibreInit(); import { useMapStories } from '../hooks/useMapStories'; import { useMapZoom } from '../hooks/useMapZoom'; import { useMapTileCache } from '../hooks/useMapTileCache'; @@ -43,6 +39,10 @@ import { lightImpact } from '../utils/haptics'; import { STYLE_ANCIENT, STYLE_MODERN } from '../constants/mapStyles'; import { buildPlaceToStoriesMap } from './MapScreen'; +// The dispatcher (MapScreen.tsx) gates on isMapNativeAvailable() before +// this module loads. Run module-level init so tiles can be fetched. +ensureMapLibreInit(); + // Roughly the Fertile Crescent — Israel through Turkey, Egypt, Iraq. // Used as default camera and as the pre-cached tile region (#1321). export const BIBLICAL_REGION = { diff --git a/app/src/utils/verseOfDay.ts b/app/src/utils/verseOfDay.ts index d2f51b217..219b55b05 100644 --- a/app/src/utils/verseOfDay.ts +++ b/app/src/utils/verseOfDay.ts @@ -12,8 +12,8 @@ */ import dailyEncouragements from '../data/dailyEncouragements'; -import { computeMovableHolidays } from './computeMovableHolidays'; import { fixedHolidays, movableHolidays, type HolidayContent } from '../data/holidayOverrides'; +import { computeMovableHolidays } from './computeMovableHolidays'; // ── Types ───────────────────────────────────────────────────────── diff --git a/content/meta/journeys/thematic/gospel-resurrections.json b/content/meta/journeys/thematic/gospel-resurrections.json new file mode 100644 index 000000000..4979ef2a6 --- /dev/null +++ b/content/meta/journeys/thematic/gospel-resurrections.json @@ -0,0 +1,110 @@ +{ + "id": "gospel-resurrections", + "journey_type": "thematic", + "title": "How Each Gospel Tells the Resurrection", + "subtitle": "Four accounts, one event, four emphases", + "description": "Few questions unsettle new Bible readers more than the apparent contradictions among the four Gospel resurrection narratives. Mark ends with terrified women fleeing an empty tomb and telling no one. Matthew adds an earthquake, guards, and a Great Commission on a Galilean mountain. Luke places every appearance in or near Jerusalem and climaxes with an Ascension. John gives us a private encounter between Jesus and Mary Magdalene, a locked-room appearance to the disciples, and a seaside breakfast in Galilee. Who arrived at the tomb first? How many angels were there? Where did Jesus appear? The differences are real, and dismissing them does no service to the text.\n\nBut the differences are also the point. Each evangelist shaped his resurrection account to serve the theological vision of his entire Gospel. Mark, who has stressed the messianic secret throughout, ends with silence and awe -- the reader must decide what to do with the empty tomb. Matthew, who presents Jesus as the authoritative teacher of a new Israel, ends with a commission to make disciples of all nations. Luke, the historian of salvation reaching outward from Jerusalem, keeps Jesus in the holy city until the Ascension sends the disciples into the world. John, whose Gospel is built around signs that reveal Jesus' glory, gives us intimate, recognition-based encounters where seeing and believing are the central drama.\n\nThis journey walks through the common core that all four accounts share, then examines each Gospel's distinctive telling. The goal is not harmonization but appreciation: understanding why each writer told the story the way he did, and what each emphasis reveals about the risen Christ.", + "lens_id": "comparative", + "depth": "short", + "sort_order": 2, + "person_id": null, + "concept_id": null, + "era": null, + "hero_image_url": null, + "tags": [ + {"type": "theme", "id": "resurrection"}, + {"type": "person", "id": "jesus"} + ], + "stops": [ + { + "stop_order": 1, + "stop_type": "regular", + "label": "Common Ground", + "ref": "1 Corinthians 15:3-8", + "book_id": "1_corinthians", + "chapter_num": 15, + "verse_start": 3, + "verse_end": 8, + "development": "Before examining what makes each Gospel unique, we need the bedrock they all share. Paul's letter to Corinth, written around AD 55, preserves a creedal formula he received from the earliest church: Christ died for our sins according to the Scriptures, he was buried, he was raised on the third day according to the Scriptures, and he appeared to Cephas, then to the Twelve, then to more than five hundred. This pre-Gospel tradition establishes the four-point core -- death, burial, resurrection, appearances -- that every canonical account assumes. All four Gospels place the discovery on the first day of the week. All four name women as the first witnesses. All four report an empty tomb. The disagreements that follow are variations played over this shared ground bass.", + "what_changes": "We establish the irreducible core shared by every New Testament witness: death, burial, empty tomb, appearances. This common foundation makes the variations in each Gospel meaningful rather than merely troublesome.", + "linked_journey_id": null, + "linked_journey_intro": null, + "bridge_to_next": "With the shared core in view, we turn to the earliest and most enigmatic account. Mark's Gospel ends not with triumph but with trembling -- a conclusion so unsettling that later scribes felt compelled to add to it." + }, + { + "stop_order": 2, + "stop_type": "regular", + "label": "Mark's Account", + "ref": "Mark 16:1-8", + "book_id": "mark", + "chapter_num": 16, + "verse_start": 1, + "verse_end": 8, + "development": "Mark's resurrection narrative is the shortest and most startling. Three women come to the tomb at sunrise to anoint the body. They find the stone rolled away and a young man in a white robe who delivers the announcement: 'He has risen; he is not here.' He instructs them to tell the disciples and Peter that Jesus is going ahead of them to Galilee. Then comes Mark's thunderbolt ending: 'They went out and fled from the tomb, for trembling and astonishment had seized them, and they said nothing to anyone, for they were afraid.' The oldest manuscripts end here. There are no appearance narratives, no resolution, no commission. Throughout his Gospel, Mark has emphasized the messianic secret -- Jesus repeatedly silences those who recognize him. The ending is the ultimate expression of that theme: even the resurrection is met with silence. The reader is left to complete the story with their own response to the empty tomb.", + "what_changes": "Mark presents resurrection as divine mystery that overwhelms human comprehension. His abrupt ending forces the reader into the role of witness: you have heard the announcement -- now what will you do with it?", + "linked_journey_id": null, + "linked_journey_intro": null, + "bridge_to_next": "Where Mark ends with fearful silence, Matthew opens the scene wide with cosmic drama. His account adds an earthquake, an angel descending like lightning, and terrified Roman guards -- making the resurrection a public, earth-shaking event that cannot be suppressed." + }, + { + "stop_order": 3, + "stop_type": "regular", + "label": "Matthew's Account", + "ref": "Matthew 28:1-20", + "book_id": "matthew", + "chapter_num": 28, + "verse_start": 1, + "verse_end": 20, + "development": "Matthew transforms Mark's quiet dawn into a seismic event. A great earthquake accompanies an angel who descends from heaven, rolls back the stone, and sits on it. His appearance is like lightning; the guards shake and become like dead men. The women receive the same announcement as in Mark but respond with 'fear and great joy' -- not silent flight. Jesus himself meets them on the road, and they worship him. Matthew then includes a detail unique to his Gospel: the chief priests bribe the guards to spread the story that the disciples stole the body, an anti-resurrection polemic Matthew clearly wants to refute. The Gospel climaxes on a mountain in Galilee where Jesus declares that all authority in heaven and on earth has been given to him and commissions the disciples to make disciples of all nations. Matthew, who has presented Jesus throughout as the authoritative interpreter of Torah and the fulfillment of Israel's scriptures, ends with Jesus as cosmic sovereign issuing a universal mandate.", + "what_changes": "Matthew frames the resurrection as the vindication of Jesus' authority. The empty tomb is not a mystery to sit with but a mandate to act on -- the Great Commission flows directly from the risen Lord's sovereign power.", + "linked_journey_id": null, + "linked_journey_intro": null, + "bridge_to_next": "Matthew sends the disciples outward to all nations from a Galilean mountain. Luke takes a different geographic approach entirely, anchoring every resurrection appearance in and around Jerusalem -- the city where salvation history reaches its climax and from which the church's mission will launch." + }, + { + "stop_order": 4, + "stop_type": "regular", + "label": "Luke's Account", + "ref": "Luke 24:1-53", + "book_id": "luke", + "chapter_num": 24, + "verse_start": 1, + "verse_end": 53, + "development": "Luke's resurrection chapter is the longest and most carefully structured of the four. Two men in dazzling clothes remind the women of what Jesus told them while he was still in Galilee -- in Luke, the angels do not send them to Galilee but recall what was said there. The appearances unfold entirely in the Jerusalem area. The Emmaus road encounter, unique to Luke, is a masterpiece of narrative theology: two dejected disciples walk with a stranger who opens the Scriptures to show that the Messiah had to suffer, and they recognize him only in the breaking of bread. Back in Jerusalem, Jesus appears to the gathered disciples, eats broiled fish to demonstrate his physicality, and opens their minds to understand the Scriptures. He tells them that repentance and forgiveness of sins will be proclaimed in his name to all nations, beginning from Jerusalem. The Gospel ends with the Ascension at Bethany, a scene that will be reprised in Acts 1. Luke, who writes a two-volume work tracing salvation from Nazareth to Rome, uses the resurrection to pivot from Jesus' ministry to the church's mission -- always with Jerusalem as the hinge.", + "what_changes": "Luke anchors the resurrection in Scripture fulfillment and sacramental recognition. The risen Jesus is known through the opened word and the broken bread -- patterns that will define the church's ongoing encounter with Christ.", + "linked_journey_id": null, + "linked_journey_intro": null, + "bridge_to_next": "Luke shows the risen Christ recognized in Scripture and sacrament, then departing to empower the church. John takes us in the opposite direction -- inward, toward intimate, personal encounters where the drama is not mission but recognition, grief turned to joy, and doubt transformed by sight." + }, + { + "stop_order": 5, + "stop_type": "regular", + "label": "John's Account", + "ref": "John 20:1-29", + "book_id": "john", + "chapter_num": 20, + "verse_start": 1, + "verse_end": 29, + "development": "John's resurrection narrative is the most intimate and theologically dense. Mary Magdalene comes to the tomb alone, while it is still dark -- a detail that echoes the light-and-darkness symbolism running through the entire Gospel. Peter and the Beloved Disciple race to the tomb; the Beloved Disciple sees the grave clothes and believes. But the heart of John's account is the encounter between Jesus and Mary in the garden. She mistakes him for the gardener until he speaks her name: 'Mary.' Recognition comes through personal address, not through physical sight. Jesus tells her not to cling to him because he has not yet ascended -- a statement that reframes the resurrection as the beginning of a new mode of presence, not a return to the old one. That evening, Jesus appears to the disciples behind locked doors, shows his wounds, breathes on them, and says 'Receive the Holy Spirit.' A week later, Thomas, who was absent, demands physical proof. Jesus invites him to touch, and Thomas makes the Gospel's climactic confession: 'My Lord and my God.' Jesus' reply -- 'Blessed are those who have not seen and yet have believed' -- speaks directly to every subsequent reader.", + "what_changes": "John presents the resurrection as personal revelation and relational encounter. Seeing and believing, doubt and confession, clinging and releasing -- the fourth Gospel makes the reader's own faith the culminating act of the story.", + "linked_journey_id": null, + "linked_journey_intro": null, + "bridge_to_next": "Each Gospel has given us a distinct window onto the same event. Now we step back to see what the four-fold witness reveals together -- not a problem to solve but a portrait richer than any single account could provide." + }, + { + "stop_order": 6, + "stop_type": "regular", + "label": "Synthesis", + "ref": "John 20:30-31", + "book_id": "john", + "chapter_num": 20, + "verse_start": 30, + "verse_end": 31, + "development": "John's editorial aside -- 'Jesus did many other signs that are not written in this book, but these are written so that you may believe' -- applies not only to his own Gospel but serves as a fitting coda for the four-fold witness. Each evangelist selected, arranged, and emphasized different details in service of a distinct theological purpose. Mark proclaims the resurrection as an overwhelming divine mystery. Matthew declares it the ground of Jesus' universal authority. Luke traces its fulfillment of Scripture and its power to open eyes through word and sacrament. John presents it as the occasion for personal encounter and the birth of faith. The differences are not defects in transmission but evidence of a living tradition in which four communities, under the guidance of the Spirit, told the story in ways that addressed their particular situations and theological convictions. Together they form a composite portrait more compelling than any single account. The resurrection is too large for one telling.", + "what_changes": "The four-fold witness teaches us that the resurrection resists reduction to a single narrative. Its meaning unfolds through multiple perspectives, each faithful to the event and each revealing dimensions the others leave implicit.", + "linked_journey_id": null, + "linked_journey_intro": null, + "bridge_to_next": null + } + ] +}