The Locate tab (mosque/halal/cemetery) hardcoded a single public
Overpass API endpoint. Public Overpass instances are individually
flaky under load — confirmed live: the same query returned 429 from
one mirror and 504 from the primary a few minutes apart, with no
retry or fallback, surfacing as a hard failure to the user.
Added a short fallback chain (overpass-api.de, then
overpass.kumi.systems) that retries on 429/503/504 and only reports
failure once every mirror has failed, with an honest 'try again
shortly' message distinct from a genuine empty-results state.
A third mirror (overpass.osm.ch) was tried too but dropped after
finding something worse than a failure: it returned a 'successful'
200 with 0 results for a query the other two mirrors correctly answer
with 30 (diet:halal=yes near Kuala Lumpur) — a stale/incomplete
regional replica that would have short-circuited the fallback loop
and silently told users nothing was nearby when it actually was.
e2e-khairat-lifestyle.cjs gains a tightened assertion (halal search
must return >0 results at a known-good test coordinate, not just
'results or empty') to catch this exact silently-wrong-mirror class of
bug in the future, plus a filter so expected fallback-path 429/504s
don't fail the 'no console errors' check — those are the retry
mechanism working, not a bug. Verified live against the deployed app:
30 real results returned.
Implements the researched IA fix: mobile nav UX consensus caps primary
destinations at 4-5 (uxpin.com, fintech/banking 2026 UX research), and
this app had grown to 22 tabs in one horizontally-scrolling row —
exactly the anti-pattern that research flags for choice paralysis and
slower task completion.
New structure — 5 bottom-nav hubs, grouped by what the user is actually
trying to do, not build order:
- Home: Coverage (unchanged, still the landing screen)
- Estate: Assets, Faraid, Insurance, Wassiyah, Hibah, Family Waqf,
Nominate, Claims (H2)
- Giving: Zakat, Sadaqah, Khairat
- Family: Tree, Trigger, Mutawalli, Manage (was 'Family', renamed to
avoid colliding with the hub's own label), Neighbourhood
- Daily: Prayer Times, Qibla, Quran, Locate
Each hub reveals its own sub-nav one tap in, instead of every tab
competing for space in a single row. Settings moved out of the tab
strip entirely into a header gear icon, matching the banking-app pattern
of keeping settings out of primary thumb-reach real estate. The bottom
nav is now fixed (thumb-zone), sub-nav keeps the old sticky-top position.
Cross-component navigation (nav.js requestedTab, used by the Family
Tree's 'give sadaqah in memory' link) now resolves a tab label to its
owning (hub, sub-tab) pair instead of a flat index — verified live.
This touches every E2E suite: a single click on a tab's old selector no
longer reaches it (hub, then sub-tab). Added a shared gotoTab(page, label)
helper to e2e-auth-helper.cjs encapsulating the two-step navigation, and
migrated all ~22 affected test files off direct nav-button selectors —
mechanical substitution followed by manual fixes for local clickTab
wrappers, template-literal selectors, and active-state assertions that
needed to target the new .hub-tab/.subnav structure specifically.
Full regression after migration: every suite passes (one isolated
Family Tree flake confirmed clean on rerun, unrelated to navigation).
Big round covering two different asks: Khairat/emergency infrastructure
(tightly coupled to what's already built) and a Muslim-lifestyle
companion surface (a genuinely different product, added at the user's
explicit request after being offered a smaller scope).
Khairat & emergency contacts:
- nf_khairat_memberships (per-member scheme membership) + nf_emergency_fund
(per-family shared reserve tracker) — records intent/contacts only,
never holds or moves real money.
- nf_trusted_contacts gains category (mosque/police/ambulance/hospital/
khairat/family/other) and email columns.
- New notify-emergency-contacts Edge Function, auto-invoked the moment a
mutawalli fires a member's death trigger (the one place in this app
where auto-send-on-trigger is actually correct), alongside — not
instead of — the existing heir notification. Verified with a live fire
end-to-end (e2e-emergency-notify.cjs, 5/5).
Lifestyle-companion tabs — all computed/searched live, nothing fabricated:
- Qibla: pure great-circle bearing math to the Kaaba from geolocation,
no API/key. Verified against Kuala Lumpur (293°, matches expected ~292°).
- Prayer Times: client-side astronomical calculation (single-pass solar
position, MWL angles), sanity-checked against known KL/London times
before shipping.
- Locate: mosque/halal/cemetery search via the free, keyless OpenStreetMap
Overpass API — real community-sourced results only, honest empty state
when nothing's mapped nearby.
- Quran: Surah list + Arabic/translation via the free alquran.cloud API,
fetched fresh each time, nothing stored in this app's own database.
- Neighbourhood: a join-by-code community announcement board — a
genuinely separate multi-tenant concept from the estate-planning family
structure, scoped to the user account. Found and fixed a real UX gap
during testing: the create/join form was only reachable with zero
existing neighbourhoods, with no way to join a second one.
Also found and fixed a real bug: the heir-notify and emergency-notify
status messages shared the same CSS class, breaking any script (including
the pre-existing e2e-per-member.cjs) that targeted '.notify-status'
without further filtering — gave each its own distinguishing class.
Covered by e2e-khairat-lifestyle.cjs (13/13) and e2e-emergency-notify.cjs
(5/5). Full regression: 316/316 across all suites (several transient
flakes under heavy mail-relay load during the sweep, all confirmed clean
on rerun — one led to the real class-collision fix above).