On February 11, 2026, a community member reported that prayer times were showing incorrect results for their city. What started as a single bug report uncovered three compounding bugs that had likely been present since the prayer times feature launched in November 2025. Over the next three days, our team shipped 7 bug fixes, 2 performance optimizations, and 6 accessibility improvements.
This is our first engineering blog post. We believe in transparency — when something breaks, we want to explain what happened, why, and what we learned.
Timeline
Day 1 — February 11: The Three-Bug Discovery
The initial report led us to discover three bugs that were compounding each other, making the root cause especially hard to isolate:
Bug 1: Wrong coordinates from the prayer times API. We were passing city names directly to the Aladhan prayer times API for geocoding. For ambiguous city names (like “Portland” or “Birmingham”), the API would sometimes return coordinates for the wrong city — or even a location in a different country. The fix: we switched to using our own geocoding pipeline (via Nominatim) to resolve coordinates before calling the prayer times API, so we always send exact latitude/longitude.
Bug 2: Stale form data after location changes. When a user changed their prayer calculation method, hidden form fields still held the old city name and coordinates. Cloudflare Rocket Loader’s deferred script execution made this worse — JavaScript that should have updated those fields sometimes ran out of order. The fix: we now read location data from the visible form fields at submission time, never from hidden cached values.
Bug 3: Timezone mismatch in “Next Prayer” logic. The countdown to the next prayer was comparing server time (US Central) against prayer times calculated for the user’s chosen city. For anyone in a different timezone, the “next prayer” indicator could point to the wrong prayer. The fix: all time comparisons now explicitly use the timezone of the selected location.
Day 1 continued — Non-Latin City Names
While fixing the geocoding pipeline, we discovered a second incident: cities with non-Latin names (Arabic, Devanagari, Cyrillic) would cause the Nominatim reverse-geocoding step to return the city name in the local script. When that name was later sent back to the API, it would fail with a 400 error. A one-line fix — adding Accept-Language: en to our geocoding requests — resolved this for all languages.
Day 1 continued — Performance Improvements
While we had the hood open, we addressed performance issues we had been tracking:
- Prayer times API cache extended from 15 minutes to end-of-day (prayer times don’t change within a day).
- API timeout reduced from 15 seconds to 5 seconds with graceful fallback.
- Quran player loading time improved by 10x through lazy initialization.
- Status page TTFB reduced from 800ms to 190ms by fixing N+1 database queries.
Day 2 — February 12: Accessibility and Edge Cases
VoiceOver accessibility fixes. Testing with screen readers revealed 6 issues with the prayer times countdown:
- The countdown timer lacked
role="timer"andaria-atomic, so screen readers would read partial updates. - Multiple DOM writes per tick caused announce stacking — screen readers would queue up overlapping announcements.
- The “next prayer” row had no
aria-currentattribute, making it indistinguishable from other rows. - Elements hidden with
display: nonewere still picked up by some screen readers; switched toclip-path. - No fallback text while the countdown was calculating, leaving screen reader users with silence.
Smart geocoding fallback. We found that searching for a city in the wrong country (e.g., “Nairobi” with country set to “United States”) would silently return coordinates for a random US city. We added a verification step: after geocoding, we check that the returned city name matches what was requested. On mismatch, we automatically retry with a city-only query.
Log noise reduction. Our watchdog was logging 144+ entries per day from normal prayer times API calls. We reduced this to only log when requests exceed a slow-request threshold.
Date display bug. The Gregorian date shown alongside prayer times was using the server’s date rather than the location’s local date. For users in timezones ahead of US Central, the date could be wrong after midnight their time.
Day 3 — February 13: The One-Line Fix
The most subtle bug was the last one. The prayer countdown timer had this logic:
if (isTomorrow || diff <= 0) {
diff += 86400;
}
The intent was: if we are counting down to tomorrow’s first prayer, add 24 hours. But the diff <= 0 condition also triggered at the exact moment a prayer arrived (when diff hits zero), immediately wrapping the countdown to ~24 hours instead of showing “0:00” and announcing the prayer time. This caused two cascading failures:
- The countdown appeared to “freeze” near zero, then jump to 23:59:59
- The screen reader arrival announcement (“It is now time for [prayer]”) never fired, because the timer never reached the zero state
The fix was removing three characters:
if (isTomorrow) {
diff += 86400;
}
What We Learned
1. Never trust a domain-specific API to also be a reliable geocoder. The Aladhan API is excellent for prayer time calculations, but its built-in city lookup is not a geocoding service. We should have used dedicated geocoding from the start.
2. Hidden form fields are fragile. Especially with tools like Cloudflare Rocket Loader that defer JavaScript execution. Visible state is more reliable than hidden cached state.
3. All time comparisons must explicitly specify timezone. “Now” means different things in different timezones. We had a bug where the server’s “today” was not the user’s “today.” Every new Date() and new DateTime() should have an explicit timezone when dealing with location-specific data.
4. Test with screen readers early. Our VoiceOver bugs had simple fixes, but we only found them because we specifically tested with assistive technology. For an organization serving blind and visually impaired Muslims, this is not optional — it is core to our mission.
5. One-line bugs can have cascading effects. The countdown fix was literally removing || diff <= 0, but it took careful analysis to understand why the countdown was wrapping and why the arrival announcement was silent.
By the Numbers
- 3 days of investigation and fixes (February 11–13)
- 9 commits shipped
- 7 bug fixes, 2 performance optimizations, 6 accessibility improvements
- Bug 1 had been present since launch (~3 months undetected)
- Status page TTFB: 800ms → 190ms
- Log noise: 144+ entries/day → slow requests only
- Prayer times cache: 15 min → end-of-day
We take reliability seriously. If you notice anything wrong with prayer times or any other feature, please reach out through our contact page or support portal. Every report helps us improve.
Day 4 Update — February 14, 2026
A community member reported that the countdown fix from Day 3 was not fully working — after Isha prayer, the countdown showed “23 hours, 57 minutes” for the same prayer instead of advancing to Fajr. Investigation revealed three compounding issues:
- Cached old JavaScript. The Day 3 fix was committed to the codebase but Drupal’s JS aggregation was still serving the old version. A cache rebuild (
drush cr) was needed to push the fix to browsers. - Phone sleep breaks timers. When a phone is locked,
setIntervalandsetTimeoutare paused by the browser. When the user unlocks their phone, the stale timer resumes with outdated state. We added avisibilitychangeevent listener that recalculates the next prayer from scratch whenever the page becomes visible again. - Timer race condition. In
startCountdownUpdate(), the immediate call toupdateCountdown()could trigger prayer arrival detection (clearing the interval and scheduling a timeout), but the code then continued to create a second competing interval. We now track the arrival timeout explicitly and only start a new interval if arrival wasn’t triggered.
We also conducted a comprehensive accessibility audit of the entire prayer times feature and fixed several issues:
- Location error messages now use
aria-live="assertive"to interrupt the screen reader immediately instead of waiting. - The “Tomorrow’s Prayer Times” notice now has
aria-live="assertive"so blind users are told when the display switches. - The close button × character is now hidden from screen readers (previously announced as “times”).
- Prayer arrival announcements are held for 10 seconds instead of 5, giving screen readers enough time to finish speaking.
- Error messages now include a visible border in addition to color, helping users with color vision deficiency.
Key takeaway: Always clear your cache after deploying JS fixes. And always test with the phone locked/unlocked — timer-based features behave very differently when the browser throttles background activity.