Jeffrey JohnsonRSS

AI-written, grammatically correct.

Shipped · April 21, 2026 · App Store

PrayerMapv1.0.5

Live on the App Store: 88 type-safety fixes, error boundaries, Reanimated migration, Apple Watch sync.

PlatformsiOSwatchOSvisionOSmacOS
StackExpo SDK 54React NativeTypeScriptSupabaseMapboxLiveKitSkiaReanimatedSwiftUI

PrayerMap v1.0.5 went live on the App Store on April 21, 2026, the same day Build 152 was submitted. This post covers what's in the build and why it took three months between the v1.0.0 launch in January and this update.

The short version: this release is almost entirely about reliability. The kind of work that makes the app trustworthy rather than impressive.

What's in this build

  • ~88 legacy any casts removed across stores, components, and library code. TypeScript strict mode is now enforced end-to-end.
  • Error boundaries added to the highest-traffic screens: map, feed, groups, explore, profile, conversations, channels, and group detail.
  • Reanimated migration: legacy Animated API calls replaced with react-native-reanimated 4.1 for smoother gesture interactions and memorial connection line rendering.
  • Native module stability fix that resolved an intermittent crash on cold launch.
  • Mapbox TurboModule patch for iOS build compatibility with the New Architecture.
  • Video prayer upload hardening: more reliable uploads for audio and video prayers.
  • User blocking from the user actions sheet.
  • Prayer updates: follow individual prayers, post updates to them, receive daily nudges.
  • Apple Watch fixes: connectivity now activates on launch, resolving a bug where paired-device data sync silently failed. Copy fixes on the Send Prayer screen.
  • Reminder screen no longer shown to returning users. Connection line drawing smoother. Rating fade timing corrected. /messages route fixed.

Tech stack

Note

Core: Expo SDK 54 (~54.0.31), React Native 0.81.5 (New Architecture enabled), TypeScript strict mode

Backend: Supabase (@supabase/supabase-js ^2.83.0) with Postgres + PostGIS + real-time subscriptions + RLS, Sentry for observability

Mapping: Mapbox GL via @rnmapbox/maps ^10.2.10

Real-time: LiveKit (@livekit/react-native ^2.9.6, livekit-client ^2.17.0) for audio/video prayers, Supabase real-time for live map updates

Graphics: Skia (@shopify/react-native-skia 2.2.12), Reanimated (~4.1.1), NativeWind (^4.2.1)

Watch: SwiftUI companion app with WatchConnectivity bridge, compiled via custom Expo plugin

Build: EAS Build (production profile), OTA updates via expo-updates

The watch story

PrayerMap's Apple Watch companion is a SwiftUI app that lets you send prayers from your wrist. The architecture is a custom Expo plugin (withWatchApp) that conditionally includes the watch target in production EAS builds, gated behind an INCLUDE_WATCH_APP environment variable so local development isn't slowed by watch compilation.

The watch source lives in apps/mobile/watch/ and gets copied into the iOS build during prebuild. The watch and phone communicate through a WatchConnectivityManager written in Swift, bridged to React Native via a native module. The v1.0.5 fix was straightforward but important: connectivity session activation was deferred until a user action, but needed to happen at app launch for the data sync to work. Without this fix, the watch appeared paired but never received data.

Error boundaries as a design decision

Adding error boundaries to eight screens sounds mechanical, but the reasoning matters for this app specifically. PrayerMap is a spiritual tool; people use it when they're vulnerable. A blank screen or a crash during prayer is not a minor UX issue. Wrapping high-traffic screens means a failure in the groups module doesn't affect someone mid-prayer on the map. The tradeoff is more boilerplate, but for this app, reliability is the feature.

Why three months between releases

The 1.0.0 launch in January exposed real usage patterns. The any casts were tech debt from the initial sprint to get approved. Error boundaries were reactive: I watched Sentry sessions to identify which screens crashed most. The Reanimated migration was the largest single change: the original Animated API worked but dropped frames on older devices during connection line animations, which is the visual centerpiece of the Living Map.

Build 151 was submitted on April 20. Build 152 replaced it the next day with additional polish, and Apple approved it the same evening.

The backstory

Eighty-six days passed between PrayerMap v1.0.0 going live on January 25 and v1.0.5 shipping on April 21. That gap wasn't silence. It was the unglamorous part of building software that people actually use. Type safety sweeps that removed 88 any casts (6e7ac6a). Error boundaries threaded through eight screens (652bcc6). A full Animated-to-Reanimated migration. A watchOS companion app written from scratch in SwiftUI (97109e8). None of it visible from the App Store listing.

There was also an AR system and a 3D globe. The AR work alone ate 15 crash-fix commits in a single afternoon (a255ba0 through 5f0a1a9), the kind of session where you fix one thing and discover the next thing was always broken, just masked. The globe was the opposite: quiet, steady, working on the second try. The count of changes between these two builds isn't the story. The judgment calls about what to ship, what to revert, and what to defer are.

In the span of v1.0.0 to v1.0.5, most of the work was invisible to users. That's the point. The app they opened on day one and the app they open today should feel the same, except the second one doesn't crash when they do something unexpected.

What almost broke this

The AR system was the closest call. A crashing component in one feature can stall an App Store review for the entire binary. Fifteen crash-fix commits in one afternoon is what "fix forward until it's stable" looks like under real pressure. Each fix revealed the next edge case. Some were device-specific. Some only surfaced in release builds. The AR feature shipped, but it was the one thing that nearly held v1.0.5 hostage.

The ErrorBoundary revert tells a different kind of story. Commit 0db934a introduced a fix that turned out to be worse than the original bug: it silently swallowed errors instead of surfacing them. Commit 7882a39 rolled it back. Sometimes the right engineering decision is to undo your own work and try again with better information. That revert is honest, and it's the kind of move that keeps the codebase trustworthy.

What I learned

Post-launch discipline is the part most indie developers struggle with, and most abandon. The launch gets the attention. The twelve weeks after launch decide whether the app is real or a portfolio piece. Users don't care about your version number. They care that the app didn't crash on the thing they opened it for today. Every any cast I removed, every error boundary I added: none of that moves a needle on any dashboard. It just means someone's prayer didn't get interrupted by a white screen.

Shipping at week one is the loud part. Shipping at week twelve is the part that earns trust. I'd rather have an app with boring patch notes and zero Sentry alerts than a flashy launch followed by silence. That's the real lesson from this release: the work that matters most is the work nobody notices.

Occasional updates when I publish. No spam.