Commit Graph

28 Commits

Author SHA1 Message Date
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 78e025c27d docs: update personas/journeys with everything built today
Adds a 7th persona (Emergency/Community Contact — mosque, khairat
officer, police, ambulance, notified automatically the moment a trigger
fires) and extends every existing journey with what shipped since the
last version: Zakat, Khairat, Sadaqah, jurisdiction/professional review,
draft will PDF, and the prayer-times/Qibla/locate/Quran/neighbourhood
lifestyle tools. New footnote clarifying Neighbourhood is scoped to the
account, not the estate-planning family.
2026-08-14 14:28:35 +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
wmj 7ce1121b94 feat: add stickiness round — daily Sadaqah tracker + Family Tree social loop
Two features scoped from the stickiness brainstorm, sharing one digest
notification pipeline:

Sadaqah tracker (new tab): a private daily giving journal, not a payment
processor — the app logs, it never moves money. Streak tracking follows
the proven pattern from dedicated apps (Sidq, Daily Sadaqa). Family
visibility is strictly limited to streak counts via a SECURITY DEFINER
RPC (nf_family_sadaqah_streaks) — amounts, causes, and notes never cross
the member boundary, respecting the Islamic preference for giving
privately. Entries can be dedicated 'in memory of' a deceased person from
the Family Tree, with a memorial count (nf_memorial_sadaqah_count, count
only) surfacing on that person's card.

Family Tree social loop: a 'give sadaqah in memory of' link on every
deceased person's card (cross-tab jump via a small requestedTab store in
nav.js), plus completeness hints (missing birth date, missing photo,
no relationships linked) feeding directly into the existing Coverage
Dashboard recommendations engine rather than a new UI surface.

Daily digest Edge Function + pg_cron (07:00 UTC): batches into one email
per family member per day, sent only when there's real content — tree
activity in the last 24h, a birthday/death-anniversary today, or a
weekly sadaqah recap on Sundays. An empty day sends nothing, deliberately
avoiding the notification-spam failure mode the research flagged.
Protected by a shared secret header since it's cron-invoked, not
user-triggered. Verified with a live manual invocation before relying on
the schedule.

Found and fixed two real bugs in nf_family_sadaqah_streaks during E2E
testing: an ambiguous unqualified 'member_id' column reference colliding
with the function's OUT parameter (42702), and a bigint/int type mismatch
from count(*) (42804) — both would have 400'd on every call in production.

Covered by e2e-sadaqah-tree.cjs (12/12). Full regression: 280/280 across
all suites.
2026-08-14 13:42:36 +08:00
wmj 9767c0d626 docs: add personas and user journey map
Six personas mapped against the app's actual roles and features: Head of
Family (owner), Family Member, Estate Agent/Mutawalli, the multi-family
professional-mutawalli variant, plus two no-account personas reached only
through data others enter — the Heir/Beneficiary (notified by the
notify-heirs email relay once a trigger fires) and the Professional
Reviewer (tracked via the Wassiyah review-request workflow). Each journey
is stage-by-stage through real tabs, not generic template steps.
2026-08-14 12:52:31 +08:00
wmj cc8288f127 docs: date the competitive benchmark (2026-08-14) 2026-08-14 12:38:24 +08:00
wmj 97a1d2ebe2 feat: close final competitive gap — professional review workflow + multi-country (MY/SG/UK)
Closes the 4th and final gap from the competitive benchmark: the
human-in-the-loop professional tier every commercial competitor pairs
with their software. Deliberately NOT a marketplace or payment
integration — a structured review-request status on each member's
Wassiyah (not_requested -> requested -> reviewed, with reviewer name
recorded), surfaced on the Mutawalli dashboard so nothing quietly ships
as final without the legally-required review being flagged.

Paired with a new whole-app jurisdiction setting since 'necessary in
certain countries' only makes sense per-jurisdiction:

- nf_families.jurisdiction (MY/SG/UK), owner-only to change — first
  UPDATE policy ever added to nf_families, which previously had none.
- New Settings-tab jurisdiction selector (JurisdictionSetting.svelte).
- Wassiyah tab and the draft will document now carry jurisdiction-
  specific legal notes: Wills Act 1959 + state Syariah (Malaysia),
  Wills Act 1838 + AMLA (Singapore), Wills Act 1837 (UK). Singapore
  added as a full third jurisdiction option, not just a stub.
- Zakat calculator's currency label and Nisab default now follow the
  family's jurisdiction (RM/SGD/GBP) instead of a hardcoded RM guess.

Covered by e2e-jurisdiction-review.cjs (13/13). Full regression:
253/253 across all suites (2 reruns confirmed as pre-existing
parallel-load flakes, not regressions).
2026-08-14 12:37:25 +08:00
wmj 71aa4ae47c feat: close 3 more competitive gaps — recommendations engine, multi-madhab Faraid, draft will PDF
Closes 3 of the remaining 4 gaps from the competitive benchmark:

- Coverage Dashboard now has a rule-based recommendations engine
  (recommendations.js) surfacing plain-language next steps from data
  already in the app — exposed assets, missing Wassiyah, unverified
  assets, unlinked liabilities, missing insurance/Zakat setup,
  insufficient attestors, no agent assigned, no Waqf configured.

- Faraid Calculator gains a madhab selector (Shafi'i/Hanafi/Maliki/
  Hanbali) encoding the one well-documented divergence this engine's
  existing rules touch: whether radd extends to a sole-heir spouse
  (Hanafi: yes; Shafi'i/Maliki/Hanbali: no, residue unallocated).
  Ja'fari (Shia) is honestly gated as unsupported rather than silently
  computed with Sunni rules, since it's a structurally different
  classification system, not a parameter tweak. faraid.test.js grows
  from 14 to 17 cases covering the divergence and the gating.

- Wassiyah tab gains a 'Generate draft will document' button producing
  a formatted, statutory-style DRAFT will (declaration/revocation,
  executor appointment, bequest schedule, witness attestation blocks)
  via browser print-to-PDF — no new PDF dependency. Clearly watermarked
  'DRAFT — NOT EXECUTED, requires physical signing and witnessing.'
  Not a claim of legal validity, a lawyer-reviewable starting point.

The 4th gap (human-in-the-loop professional tier) remains open — out
of scope for a self-serve prevention tool. COMPETITIVE_BENCHMARK.md
updated to reflect closed/partially-closed status on each item.

Covered by e2e-gaps-round2.cjs (14/14) plus 3 new faraid.test.js cases.
Full regression: 226/226 across all suites.
2026-08-14 12:15:54 +08:00
wmj 972ad7ff68 feat: add Zakat calculator (per-member, Nisab + 2.5% rate)
Closes the Zakat gap identified in the competitive benchmark against
Rafiq. Per-member module (nf_zakat_records, same author-owns/family-reads
RLS pattern as Insurance/Wassiyah/Waqf) computing 2.5% due on zakatable
wealth (cash, gold, silver, business assets, investments) once above a
user-entered Nisab threshold, minus deductible short-term liabilities.
Manual entry only, matching the app's existing philosophy — no live gold
price feed. Covered by e2e-zakat.cjs (7/7).
2026-08-14 11:55:59 +08:00
wmj 2b022cbcc3 docs: add competitive benchmark vs 5 Islamic estate-planning apps
Compares Nur Falah against Legacy Logic, MyPusaka, My Islamic Wills, True
Wills, and Rafiq. Identifies 5 gaps: legally-filable will output, Zakat
calculator, multi-madhab Faraid support, human advisor tier, AI guidance.
2026-08-14 11:50:01 +08:00
wmj 49706e0cd2 feat: add insurance/Takaful tracking, liabilities, and asset ownership verification
Per-member Insurance & Takaful tab (life/Takaful/medical/asset policies,
same author-owns/family-reads pattern as Wassiyah/Waqf), a family-shared
Liabilities section on the Asset Registry (kept distinct from assets since
Faraid requires debts settled before distribution), and dual-path asset
ownership verification (proof document + confirm by either the
mutawalli/agent or any other family member, since not every demo family
has an agent assigned). All proof documents share a new private
nf-asset-documents storage bucket with the same RLS pattern as person
photos. Mutawalli dashboard now surfaces each member's insurance policies.

Covered by e2e-insurance-verification.cjs (12/12); full regression sweep
233/233 across all suites.
2026-08-14 10:17:03 +08:00
wmj d5e25e9f8e Seed 5 fully-populated demo families with dummy assets, coverage, and trees
Per explicit request: "full dummy data on at least 5 families with dummy
assets and family members." Seeded directly against the live Supabase
backend (SQL, not driving the UI 5x) for speed and reliability, then
verified through the actual live app rather than trusting the inserts.

5 families, each with a distinct role in demonstrating the app:
- The Ismail Family (100% coverage) — near-fully-planned estate: Hibah,
  bank-mandate nomination, digital-custody (key-escrow) nomination,
  business-continuity (company shares). Has an extra member (spouse) and
  an agent/mutawalli.
- The Rahman Family (UK, 20% coverage) — diaspora market, partial
  coverage, exercises Wassiyah (charitable bequest) alongside Hibah/
  Nomination. Has an extra adult-child member.
- The Osman Family (31% coverage) — deliberately mostly UNCOVERED to
  demonstrate the Coverage Dashboard's warning state and per-asset
  suggestions; includes a sole-proprietorship business with the
  waqf-enterprise redirect note.
- The Yusuf Family (94% coverage) — young family, digital-asset-heavy
  (multisig nomination). Shares the same mutawalli/agent as Ismail —
  demonstrates one professional agent managing multiple families.
- The Karim Family (100% coverage) — Family Waqf demo: an apartment
  dedicated as corpus with a named mutawalli and successor.

Every fast-path channel type (Hibah, EPF/bank-mandate, digital-custody in
both multisig and key-escrow modes, business-continuity in both
company-shares and sole-proprietorship modes, trust, family waqf) and
every family role combination (owner-only, owner+member, owner+member+
agent, shared cross-family agent) now has at least one real example.
Each family also has a genealogy tree (3-6 people, parent/child and
spouse relationships) so the Tree tab is never empty for a demo.

DEMO_DATA.md documents all logins (shared password DemoPassword123!),
what each family demonstrates, and the verification method.

verify-demo-families.cjs: confirms all 5 families actually render
correctly in the live app, not just that the SQL succeeded — sign-in,
correct asset count, non-zero estate total, coverage percentage, correct
person count, non-empty tree render. 35/35 passing.

Also fixed a genuinely flaky e2e-uat.cjs assertion found while
re-verifying after seeding — same instant-isVisible()-after-fixed-wait
pattern already fixed elsewhere in this session, now using a proper
waitFor(). Stable across 3 consecutive runs (32/32 each). Full sweep
after seeding: e2e-fastpath 16/16, e2e-trust 12/12, e2e-business 10/10,
e2e-digital-vehicle 10/10, e2e-property 9/9, e2e-other 4/4, e2e-info
31/31, e2e-per-member 11/11, e2e-family-tree 9/9 — 209/209 total across
all suites plus the 35 demo-family checks.
2026-08-14 09:35:45 +08:00
wmj e7a111c387 Add mini family genealogy tree with per-person photos
Per explicit request: "mini family genealogy mapping with ability to add
photos on each family member." Scoped via clarifying questions: people are
lightweight records independent of login accounts (most real tree nodes —
grandparents, deceased relatives, young children — never sign up),
relationships support parent/child + spouse (enough to render a real
multi-generation tree), photos go to Supabase Storage (native to the
backend already in use, no new infrastructure).

Schema: nf_people (name, gender, birth/death dates, notes, optional
linked_user_id if the person also happens to have a real account,
photo_path) and nf_relationships (person_a/person_b + type: parent_of
directional, spouse_of symmetric). Both family-member-shared like the
Asset Registry — any owner/agent/member can view and edit, unlike the
per-author Wassiyah/Waqf model. New private Storage bucket
nf-people-photos, path convention {family_id}/{person_id}/{filename} so
RLS can check family membership straight from the path without a join.

New FamilyTree.svelte: add/edit/delete a person, upload/replace/remove
their photo (signed URLs, 1hr TTL, refreshed on every load since the
bucket is private), link/unlink relationships, and a recursive generational
tree render — roots are anyone with no recorded parent, spouses shown
inline next to their partner rather than as separate branches.

e2e-family-tree.cjs: 4-person 3-generation tree built end to end against
the live backend — add people, link parent/child + spouse relationships,
upload a real PNG to Supabase Storage and confirm it renders as the
avatar, remove it and confirm it reverts to the initial-letter fallback,
edit a person's notes, delete a relationship, and confirm a second family
under the same account sees none of this tree (isolation, same pattern
already proven for Wassiyah/Waqf). Found two real test-authoring bugs
along the way (not app bugs): a case-insensitive :has-text substring match
on "Spouse" was also matching the relationship-type select's own "is
spouse of" option text, and a person-summary click meant to inspect an
already-expanded card was instead toggling it closed. Both fixed by
selecting on DOM position/structure instead of loose text matching.
9/9 passing.

Full regression sweep: e2e-uat 32/32, e2e-fastpath 16/16, e2e-trust 12/12,
e2e-business 10/10, e2e-digital-vehicle 10/10 (one transient failure on
first run, passed clean on retry — consistent with earlier-observed
flakiness under heavy parallel test load, not a regression),
e2e-property 9/9, e2e-other 4/4, e2e-info 31/31, e2e-per-member 11/11,
e2e-family-tree 9/9 — 174/174 total.
2026-08-14 09:16:42 +08:00
wmj 5adbad6d53 Real SMTP heir notifications — no third-party signup needed
Per explicit direction to use existing VPS/Bitwarden/Gitea infrastructure
instead of waiting on a Resend signup. Bitwarden's MCP unlock/list both
hung (server-side issue, confirmed via direct CLI retry too — not
something to keep retrying), so this used the VPS and Gitea directly.

Found real, working infrastructure already in place: a documented but
never-deployed falah-ibaas email connector (SMTP wrapper) at
/opt/falah-ibaas/connectors/email, backed by a local Postfix relay that
Ghost already uses successfully in production on the same VPS
(mail__options__host=172.17.0.1:25, no auth needed internally).

Built nf-mail-relay: a small HTTP wrapper (Python stdlib, no deps) around
that connector, deployed as a Docker Swarm service on the existing
Traefik network at https://nfmailrelay.falahos.my, bind-mounting the
connector code read-only so it stays in sync with any future updates to
it. Shared-secret bearer auth (X-Relay-Secret) — verified a wrong secret
gets rejected with 401.

notify-heirs Edge Function rewired to call this relay instead of Resend.
Two real bugs found and fixed via actual testing, not code review:
- The function only took memberId, but a person can belong to multiple
  families — .maybeSingle() against multiple trigger rows failed closed
  (correctly, but silently, as "not triggered"). Function and both
  call sites (db.js notifyHeirs, MutawalliDashboard) now require and pass
  familyId too, matching the same composite-key fix already applied to
  the trigger tables themselves.
- No CORS/OPTIONS handling: a browser's preflight OPTIONS request has no
  body, and calling req.json() on it crashed the function before any
  headers were sent — surfaced in the browser as a generic "Failed to
  send a request" with no detail. Added an OPTIONS short-circuit and
  CORS headers on every response path.

Verified with a real send to a live inbox through the full chain
(browser -> Edge Function -> VPS relay -> Postfix -> SMTP), not just a
connectivity check.

Also fixed a stale e2e-trust.cjs assertion using the same
instant-isVisible()-after-fixed-wait pattern already fixed elsewhere in
this session — real app behavior was correct, only the test's timing
assumption was wrong.

e2e-per-member.cjs's heir-notification check now asserts an actual "Sent"
result via the real relay instead of accepting either Sent or a
not-configured failure. Full sweep: e2e-uat 32/32 (stable across 3 runs,
one earlier run's failure was a one-off network blip under heavy parallel
test load), e2e-fastpath 16/16, e2e-trust 12/12 (stable across 3 runs),
e2e-business 10/10, e2e-digital-vehicle 10/10, e2e-property 9/9,
e2e-other 4/4, e2e-info 31/31, e2e-per-member 11/11 — 165/165 total.
2026-08-14 07:59:44 +08:00
wmj 9eb2ce7ce3 Per-member estate model: each family member authors their own Wassiyah/Waqf,
mutawalli executes on any member's trigger, heir email notification

Per explicit product direction: "all members of the family can make their
own wassiyah or waqif. The mutawali or the trustee agent can access and
execute those wassiyah and waqif upon any event triggers. Warith or the
heir will be automatically notified via email."

Schema: nf_family_members.role now includes 'member' (authors own documents,
doesn't manage the family). nf_wassiyah_settings/nf_wassiyah_bequests/
nf_waqf_designations gained author_id — each is now per-author, not
per-family. Added recipient_email / beneficiary_email columns for warith
notification targets. New nf_member_triggers (composite PK family_id+
member_id) and nf_member_attestors: a per-member death trigger, separate
from the legacy family-wide nf_death_triggers (kept for backward
compatibility, still exercised by existing suites).

RLS: any family member can READ any other member's Wassiyah/Waqf (the
mutawalli needs full visibility to execute), but only the document's own
author can WRITE to it — not even the owner. Firing a member's trigger
requires the caller to have role agent/owner AND not be the member
themselves (enforced in the policy's WITH CHECK, not just the UI) — matches
"the mutawalli executes, never for themselves."

New UI: FamilyManagement gained a role selector (member vs agent) on
invites. New MutawalliDashboard.svelte — the trustee's execution surface:
pick any family member, see their Wassiyah/Waqf read-only, set up
attestors + death cert ref, fire their trigger (blocked for self both by
disabled UI and by RLS), then trigger heir email notifications.

Edge Function notify-heirs deployed (Deno, uses Resend): reads the
triggered member's Wassiyah recipients and Waqf beneficiaries wherever an
email was recorded, sends each a notice. Returns a clear 501 rather than
failing silently until RESEND_API_KEY is set as a project secret.

Three real bugs found via testing against the live backend, not visible
from code review alone:
- listFamilyMembers() never selected user_id — every member-scoped lookup
  on the new dashboard was silently keying off undefined.
- nf_member_triggers keyed by member_id alone: since a person can belong
  to multiple families, firing a trigger in one family marked them
  "triggered" in every other family they belong to. Fixed to composite
  (family_id, member_id) key.
- Classic Svelte 5 $state pitfall: (proxyObject[key] ??= []).push(item)
  mutates the plain array literal the ??= expression evaluates to, not
  the proxy-wrapped array Svelte actually tracks — so pushed items were
  silently invisible to the UI forever. Fixed by building on a plain
  object and assigning to the $state variable once. Also found and fixed
  the same design smell in the older WassiyahGenerator/FamilyWaqfDesignator
  authorId handling: it was snapshotted once via currentUser()?.id at
  mount instead of read live off the session store, which could silently
  break writes on a remount that happened before session hydration
  finished — now reads live and guards refresh() on it being present.

CoverageDashboard and DeathTrigger updated to check ANY family member's
Waqf corpus for coverage (not just one author's), since coverage is a
family-wide view even though authorship is per-member now.

e2e-per-member.cjs: new suite covering the full flow — owner invites a
member and an agent; member authors a private Wassiyah (invisible to
other members, confirming per-author isolation); mutawalli sees it on
their dashboard and fires the member's trigger; member cannot fire their
own; heir notification call completes with either Sent or a clear
"not configured" failure, never hangs. 11/11 passing.

Full regression sweep after these changes: e2e-uat 32/32 (stable across
3 consecutive runs), e2e-fastpath 16/16, e2e-trust 12/12, e2e-business
10/10, e2e-digital-vehicle 10/10, e2e-property 9/9, e2e-other 4/4,
e2e-info 31/31, e2e-family-agent 12/12 (updated for the new invite-form
role selector), e2e-per-member 11/11 — 178/178 total, no regressions.
2026-08-14 05:51:46 +08:00
wmj b9a98bd97a Restore all 8 pre-auth E2E suites with a sign-in prelude; fix bugs they found
New e2e-auth-helper.cjs: shared signInFreshFamily() prelude — signs in as
the confirmed test owner account and creates a uniquely-named family per
run, so accumulated data from a previous run's assets/hibah/etc. (now
persisted in Supabase, not wiped with the browser context like localStorage
was) can never bleed into another run's percentage/coverage assertions.
All 8 suites (e2e-uat, e2e-fastpath, e2e-trust, e2e-business,
e2e-digital-vehicle, e2e-property, e2e-other, e2e-info) now call it in
place of the old anonymous page.goto(BASE).

Restoring them surfaced two real product bugs, not just test staleness:

1. WassiyahGenerator.svelte was never migrated to Supabase in the earlier
   backend work — it still called the old local storage.js load()/save()
   for assets, bequests, and witnesses, so the one-third meter silently
   read an empty local cache and always showed 0. Migrated to family-scoped
   Supabase tables (new nf_wassiyah_bequests, nf_wassiyah_settings, with
   member-scoped RLS) matching the pattern used for Hibah/Nominations/etc.

2. storage.js's exportAll() did an unguarded JSON.parse on every
   "nf."-prefixed localStorage key, but family.js stores activeFamilyId as
   a raw string (not JSON-encoded) — one malformed parse threw and silently
   aborted the whole export before the file download fired. Made exportAll
   defensive: falls back to the raw string on a parse failure instead of
   throwing.

The remaining test failures were async-timing gaps inherent to the move
from synchronous localStorage reads to async Supabase fetches: several
assertions checked <select> option counts or newly-created rows immediately
after a fixed short wait, before the async load/refresh had actually
landed. Fixed by replacing blind isVisible()/fixed-timeout checks with
proper waitFor()/polling in the test helpers (selectByText, corpus-select
population, row-creation checks) — not a product bug, but worth fixing
since the old timing assumptions no longer hold now that data is live and
shared instead of instant and local.

Results: e2e-uat 32/32, e2e-fastpath 16/16, e2e-trust 12/12,
e2e-business 10/10, e2e-digital-vehicle 10/10, e2e-property 9/9,
e2e-other 4/4, e2e-info 31/31 — 124/124. Re-verified e2e-family-agent
(12/12) and e2e-smoke-authed (24/24) still pass after the
WassiyahGenerator migration. 160/160 total across all ten suites.
2026-08-13 21:30:46 +08:00
wmj a0c70e1411 Add estate agent delegation: real accounts, multi-tenant backend, RLS-enforced roles
Per explicit product direction: the app assumed a single user (head of
family) with everything in per-device localStorage. There was no way for
a family to delegate estate management to an agent (relative or
professional) without literally handing over the device. This required
real backend infrastructure, not a UI addition — added Supabase
(Postgres + Auth) as a multi-tenant backend.

Schema (nf_ prefixed to stay isolated from other tables in the reused
"Falah OS demo" project): nf_families, nf_family_members (role: owner/
agent, status: invited/active), and family-scoped versions of every
estate table — nf_assets, nf_trusted_contacts, nf_hibah_gifts,
nf_waqf_designations/nf_waqf_beneficiaries, nf_nominations,
nf_attestors, nf_death_triggers.

Permission model, enforced by RLS at the database level (not just
hidden in the UI): an agent can do everything an owner can — add/edit
assets, draft Hibah/Waqf/Nominations, set up the Death Trigger — except
fire it. nf_death_triggers' UPDATE/INSERT policies use a WITH CHECK that
only allows triggered=true when the caller has role='owner' on that
family. A professional agent can be invited to multiple families and
switches between them from their own dashboard.

New: auth.js, family.js, db.js, AuthScreen.svelte, FamilySwitcher.svelte,
FamilyManagement.svelte (new "Family" tab: invite agents, see members,
switch families). AssetRegistry, HibahTracker, FamilyWaqfDesignator,
NominationRegistry, CoverageDashboard, and DeathTrigger all migrated
from storage.js (localStorage) to db.js (Supabase), scoped to the
active family_id. App.svelte now gates on auth + family selection
before showing the main tab shell.

Three real bugs found and fixed via testing against the live backend
(not caught by the old localStorage-based suites, which had no
cross-client concurrency to expose them):
- RLS gap: pending-invite lookup joins nf_families(name), but the
  invitee isn't a family member yet, so the join was silently dropped —
  added a policy letting a pending invitee see just the family name.
- Attestor row race: lazy "create on first blur" could double-fire from
  two different code paths, creating duplicate rows and confirming the
  wrong one. Fixed by eagerly creating attestor rows on first load so
  every row always has a real id — no more create-or-update ambiguity.
- Out-of-order async clobber: three death-trigger setup fields each
  fired a full-snapshot upsert on every input; whichever request
  *finished* last (not fired last) won, silently reverting the other
  two fields to stale values. Fixed with per-field partial updates
  (updateDeathTriggerField) that can't clobber columns they don't touch.

e2e-family-agent.cjs: full owner/agent flow against the live Supabase
backend — invite, accept, shared live data, agent blocked from firing
the trigger (button stays disabled and a direct RLS-level attempt would
also fail), owner successfully fires it. 12/12 passing.
e2e-smoke-authed.cjs: post-auth-gate sweep confirming every existing tab
still renders and its info panel still opens under the new sign-in
requirement. 24/24 passing, zero console errors.

Known follow-up, not done here: the eight pre-auth E2E suites
(e2e-uat.cjs, e2e-fastpath.cjs, e2e-trust.cjs, e2e-business.cjs,
e2e-digital-vehicle.cjs, e2e-property.cjs, e2e-other.cjs, e2e-info.cjs)
assume an anonymous landing page and need a sign-in prelude added
before they're valid again — their detailed assertions were re-verified
functionally via the smoke test and manual review, not by running them
as-is.
2026-08-13 21:07:23 +08:00
wmj 4250f8da14 Fix "Other" asset type: was suggesting a channel that doesn't exist
nonprobate.js's suggestedChannel() had no case for the "Other" type (or
anything else that fell through every specific check) — it defaulted to
{ channel: 'nomination', label: 'EPF / Takaful nomination' }. Two bugs:
(1) recommending a retirement/insurance nomination for a generic asset
that could be anything (a collectible, a domain name, an heirloom) makes
no sense, and (2) 'nomination' isn't a real value in NominationRegistry's
CHANNEL_TYPES (epf/takaful/bank-mandate/trust/business-continuity/
digital-custody) — the suggestion pointed at a channel that doesn't exist.

Default now points to Hibah — the safest general-purpose fast path since
it needs no institution or legal structure, with a note to consider a
trust setup instead if the asset is high-value or income-generating.

e2e-other.cjs: new suite confirming the fixed suggestion, the corrected
in-app reasoning text, and that following it (Hibah) actually reaches
100% coverage. 4/4 passing. Re-verified all seven prior suites (32/32,
16/16, 12/12, 10/10, 10/10, 9/9, 31/31) — 136/136 total, no regressions.
2026-08-13 18:33:22 +08:00
wmj 10289e2d92 Verify property/land coverage across all three applicable fast-path channels
No code changes needed — land/property was already correctly wired through
Hibah (offer/acceptance/possession now), Waqf (dedicated corpus via Family
Waqf Designator), and Trust (via Nomination Registry, added earlier this
session). This adds explicit isolated verification since prior tests only
exercised one channel per property asset at a time.

e2e-property.cjs: three separate property assets, each covered through a
different channel, confirming Coverage Dashboard correctly attributes each
(Hibah/Waqf/Trust) and reaches 100%, and Death Trigger generates a
distinct execution packet for all three. 9/9 passing. Re-verified
e2e-uat.cjs (32/32), e2e-fastpath.cjs (16/16), e2e-trust.cjs (12/12),
e2e-business.cjs (10/10), e2e-digital-vehicle.cjs (10/10), e2e-info.cjs
(31/31) — 132/132 total, no regressions.
2026-08-13 18:18:40 +08:00
wmj 4d244db6d0 Add (i) info button to every tab, explained for the average user
New InfoPanel.svelte: shared (i) button next to each module's h2, toggling
a plain-language explainer with three sections — what this tab is, how to
use it, what to enter in each field. One component so tone stays
consistent across all 10 tabs instead of each screen inventing its own
help pattern.

Deliberately avoids fiqh/legal jargon in favor of concrete, everyday
framing (e.g. Hibah explained as "a gift you give right now, while you're
alive" rather than leading with the Arabic term) — written for someone
with no prior estate-planning or Islamic finance background, consistent
with the "convince a 100-year-old grandmother" usability bar already
established for this product.

Wired into: Coverage, Faraid, Assets, Wassiyah, Hibah, Family Waqf,
Nominate, Trigger, Claims (H2), Settings.

e2e-info.cjs: new suite verifying the info button appears, opens a
non-trivial explanation, and closes again on every one of the 10 tabs.
31/31 passing. Re-verified all five prior suites (32/32, 16/16, 12/12,
10/10, 10/10) — 111/111 total, no regressions.
2026-08-13 18:14:28 +08:00
wmj 07b9f4f07a Cover digital assets and vehicles with proper fast-path instruments
Digital assets: nonprobate.js's suggestion previously claimed channel
'bank-mandate' with label 'Multi-sig / pre-authorized release' — a
mismatch, since bank-mandate's fields (institution/nominee/reference)
don't fit crypto custody at all. New "digital-custody" channel with
custody-type-specific fields: exchange beneficiary feature, self-custody
multi-sig (warns that it only works if a living co-signer is already
configured), or key-escrow with executor (warns explicitly: never store
the actual seed phrase or private key in this app or any single record —
only a reference to WHERE access is arranged).

Vehicles/jewelry/valuables: were lumped into the same "trust" suggestion
as land, which is overkill for movable property that hands over cleanly
by a simple Hibah. Suggestion now points straight to Hibah for these;
land keeps the trust suggestion since it has no such easy path.

CoverageDashboard's suggestion-inline text simplified from an ad-hoc
ternary chain into a clean NEXT_STEP_TAB map, now correctly covering
hibah/trust/digital-custody/business-continuity.

e2e-digital-vehicle.cjs: new suite verifying digital asset exposed ->
custody-type warnings -> export -> covered, and vehicle exposed -> Hibah
suggestion (not trust) -> covered via Hibah link -> 100% combined
coverage. 10/10 passing. Re-verified all four prior suites (32/32,
16/16, 12/12, 10/10) — 80/80 total, no regressions.
2026-08-13 17:41:25 +08:00
wmj 051c9d9a94 Cover business interests via a proper continuity instrument
Same gap as land parcels: business assets were routed into the generic
trust fields (trustee/successor/beneficiaries), which don't fit — a
business needs a continuity instrument, not a trustee holding title.

New "business-continuity" channel in Nomination Registry, modelled on the
digital-waqif docs repo's own business-succession framework (Clause 9):
- Structure selector (sole proprietorship / partnership / company). Sole
  proprietorship surfaces an explicit warning: it legally dies with the
  owner, so the only fast-path options are converting to a company or
  dedicating as a waqf-owned enterprise.
- Instrument selector: partnership continuation clause, shareholder
  buy-sell agreement (the standard fast-path for company shares, often
  Takaful-funded), or a direct redirect to Family Waqf Designator for a
  waqf-owned enterprise (nothing duplicated here — reuses that module).
- Dedicated business-continuity-draft.txt export, same treatment as the
  trust deed and waqfiyya exports.
- Death Trigger packet text updated with the business-specific execution
  action (execute buy-sell / continuation clause, file Takaful claim if
  funded — a private agreement between owners, not a probate filing).

e2e-business.cjs: new suite covering exposed->warning->instrument
selection->export->100% coverage->execution packet, plus the
waqf-enterprise redirect disabling the Add button. 10/10 passing.
Re-verified e2e-uat.cjs (32/32), e2e-fastpath.cjs (16/16), and
e2e-trust.cjs (12/12) — 70/70 total, no regressions.
2026-08-13 17:32:32 +08:00
wmj ff47dc9ed2 Cover land parcels via proper trust setup, not generic nomination fields
Nomination Registry's "trust" channel type previously reused the same
nominee/institution fields as EPF/Takaful/bank nominations — wrong shape
for a trust, which needs a trustee (and successor) holding title, not a
beneficiary named on a policy. Coverage math already counted any
nomination-type entry as fast-path regardless of shape, so land parcels
were silently "covered" without the actual structure a trust requires.

Now: selecting "trust" swaps in trustee/successor-trustee/beneficiaries
fields, and adds a dedicated trust-setup drafting-aid export (same
treatment as the Waqf deed export) explaining what still needs a real
trust company/lawyer to execute.

e2e-trust.cjs: new suite verifying the full path for a land parcel —
exposed -> trust suggestion -> trustee fields -> deed export -> coverage
flips to 100% -> death trigger generates its execution packet. 12/12
passing. Re-verified e2e-uat.cjs (32/32) and e2e-fastpath.cjs (16/16)
with no regressions.
2026-08-13 17:27:51 +08:00
wmj ad2b3c48a3 Rebuild around the real mechanism: bypass Faraid/probate entirely
Product direction clarified: Faraid/probate court adjudication is what takes
~2 years. Assets already moved out of the estate before death (via completed
Hibah, dedicated Waqf, or a legally-direct nomination channel) are never in
that queue — there's nothing for a court to adjudicate. The app's job is to
maximize what's covered by those instruments and make the death-triggered
payout fast for whatever is covered.

New:
- nonprobate.js: coverage model — classifies each Asset Registry entry by
  fast-path channel (hibah/waqf/nomination) vs exposed (faraid/probate).
- CoverageDashboard.svelte: the single number that matters — % of estate
  that bypasses Faraid entirely, with per-asset next-step suggestions.
- NominationRegistry.svelte: third fast-path channel for assets that can't
  be fully gifted/waqf'd — EPF (EPF Act 1991 s.51), Takaful/insurance
  (Insurance Act 1996 s.166), bank death-mandate, trust/nominee holding.
- DeathTrigger.svelte: the execution layer. Multi-attestor threshold +
  death certificate reference fires the trigger; generates a ready-to-file
  execution packet per covered asset, pre-filled from Hibah/Waqf/Nomination
  records. Explicitly scoped: this app cannot itself move money or transfer
  title, but removes missing-paperwork/ambiguity as a source of delay so
  institutions can act in days instead of stacking behind a probate queue.

Changed:
- HibahTracker/FamilyWaqfDesignator: now link to a specific Asset Registry
  entry so coverage can be computed; fast-path explanation added to both.
- WassiyahGenerator: explicit warning that a wassiyah does NOT bypass
  probate — it's a post-death instrument, only appropriate for the
  discretionary one-third, not a fast-track vehicle. Users were previously
  not told this distinction.
- FaraidCalculator: reframed as informational-only, showing what applies to
  whatever remains uncovered — not itself a mechanism.

e2e-fastpath.cjs: new Playwright suite covering the full mechanism
end-to-end (coverage 0%->100%, nomination, multi-attestor trigger threshold,
execution packet generation) — 16/16 passing. Original e2e-uat.cjs suite
re-verified at 32/32 with no regressions.
2026-08-13 17:21:44 +08:00
wmj 3badc5caaa Fix E2E test bugs (regex selectOption, tab context) — full suite now 32/32 green 2026-08-13 17:09:23 +08:00
wmj eb1ce59159 Wire language switcher into Settings; add E2E UAT suite
- App.svelte: language toggle (EN/BM) wired to existing i18n.js store,
  header tagline reflects selection. Was previously dead code with no
  UI control (found by e2e-uat.cjs during full-flow UAT).
- e2e-uat.cjs: Playwright-driven E2E UAT against the live deployment —
  real clicks/typing through all 7 tabs, faraid textbook-case
  verification, heir-exclusion blocking, marad al-mawt guard, 1/3 cap
  override flow, claim issuance/transfer, export/delete-all guards,
  PWA and console-error checks.
2026-08-13 17:08:41 +08:00
wmj 6690696f7f Initial build: Horizon 1 Prevention Suite + Horizon 2 Unlock demo
Svelte 5 + Vite PWA, styled to match moslem03.falahos.my's design system.

Horizon 1: Faraid Calculator (shared calc core, 14 classical cases passing),
Asset Registry, Wassiyah Generator (1/3 meter + heir-exclusion block),
Hibah Tracker and Family Waqf Designator (shared marad al-mawt guardrail).

Horizon 2: Digital Beneficial Claims — local non-custodial demo of the claim
model and transfer restriction only.

Two governance gates in this project's own PRDs were overridden per explicit
product direction, and are flagged in-app and in README.md / deploy/DEPLOY.md
rather than silently shipped as production-ready:
- Family Waqf Designator ships ahead of scholarly sign-off (OPEN-01 in
  scholarly-review-log.md remains unresolved).
- Horizon 2 ships ahead of the PRD's stated Phase 0 gate (legal opinion +
  signed institutional partner) with no confirmation that gate is cleared.
2026-08-13 16:55:58 +08:00