4 Commits

Author SHA1 Message Date
wmj 9cb0793347 fix: Qibla not detecting device compass on iOS and Android
Two real bugs, both explaining 'didn't detect compass':

1. iOS: DeviceOrientationEvent.requestPermission() was called *after*
   the async geolocation round-trip resolved. Safari's user-activation
   grant for sensor permissions expires almost immediately, so by the
   time a GPS fix comes back (often several seconds), the permission
   call silently fails with no prompt ever shown. Now requested
   synchronously inside the button's click handler, before geolocation
   starts.

2. Android: the heading was only accepted when e.absolute === true, but
   many Android browsers fire plain 'deviceorientation' with
   absolute:false even though alpha is a perfectly usable heading —
   compass detection silently never succeeded on those devices at all.
   Now accepts alpha regardless of the absolute flag, falling back to
   it when webkitCompassHeading (iOS) isn't present.

Also fixed a duplicate-listener bug: clicking 'Refresh location'
repeatedly re-registered deviceorientation listeners without removing
the previous ones, stacking duplicates on every retry.

Verified live: dispatched a synthetic non-absolute deviceorientation
event (the exact Android failure shape) against the deployed app —
compass detection now activates correctly where it previously stayed
silently stuck on the no-sensor fallback. Added as a permanent
regression assertion in e2e-khairat-lifestyle.cjs (16/16).
2026-08-14 15:23:17 +08:00
wmj 23f32872a5 fix: Locate returning 429/504 and silently-empty halal results
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.
2026-08-14 15:14:48 +08:00
wmj 33a821e61d refactor: restructure navigation into 5 grouped hubs, off the 22-item flat strip
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).
2026-08-14 14:55:09 +08:00
wmj 2549e9de0c feat: add Khairat, emergency-contact auto-notify, and 5 lifestyle-companion tabs
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).
2026-08-14 14:25:26 +08:00