From 6941be23c32adfac4d502c8efab234f74667fc16 Mon Sep 17 00:00:00 2001 From: Wmj Ismail Date: Fri, 14 Aug 2026 11:56:05 +0800 Subject: [PATCH 1/3] feat: implement ui/ux dashboard improvements (search, layout, cta) --- src/lib/FamilySwitcher.svelte | 73 ++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/src/lib/FamilySwitcher.svelte b/src/lib/FamilySwitcher.svelte index 842c206..23412cc 100644 --- a/src/lib/FamilySwitcher.svelte +++ b/src/lib/FamilySwitcher.svelte @@ -6,6 +6,7 @@ let families = $state([]); let invites = $state([]); let newFamilyName = $state(''); + let searchQuery = $state(''); let loading = $state(true); let error = $state(''); @@ -45,6 +46,7 @@ } const isAgentForMultiple = $derived(families.filter(f => f.role === 'agent').length > 1); + const filteredFamilies = $derived(families.filter(f => f.name.toLowerCase().includes(searchQuery.toLowerCase())));
@@ -65,12 +67,22 @@ {#if loading}

Loading your families…

{:else} +
+

Start your own family estate

+

If you're the head of family setting this up for the first time, create your family here. You can invite an estate agent to help manage it once it's set up.

+ + +
+ {#if invites.length}

Pending invitations

{#each invites as inv} -
- {inv.familyName} — as {inv.role} +
+
+ {inv.familyName} + Role: {inv.role} +
{/each} @@ -79,22 +91,29 @@ {#if families.length}
-

{isAgentForMultiple ? 'Your assigned families' : 'Your families'}

- {#each families as f} - - {/each} +
+

{isAgentForMultiple ? 'Your assigned families' : 'Your families'}

+ +
+
+ {#each filteredFamilies as f} + + {/each} + {#if filteredFamilies.length === 0} +

No families found matching "{searchQuery}"

+ {/if} +
{/if} - -
-

Start your own family estate

-

If you're the head of family setting this up for the first time, create your family here. You can invite an estate agent to help manage it once it's set up.

- - -
{/if}
@@ -114,12 +133,30 @@ .hint { font-size: 11.5px; color: #8A8478; line-height: 1.5; margin-bottom: 12px; } .invite-row { display: flex; justify-content: space-between; align-items: center; background: rgba(201,168,76,0.08); border: 1px solid rgba(201,168,76,0.25); border-radius: 10px; padding: 12px; margin-bottom: 8px; font-size: 13px; color: #E8E4DC; } .btn-small { background: #C9A84C; color: #070A0D; border: none; border-radius: 8px; padding: 8px 12px; font-size: 12px; font-weight: 600; cursor: pointer; } - .family-row { width: 100%; display: flex; justify-content: space-between; align-items: center; background: rgba(255,255,255,0.04); border: none; border-radius: 10px; padding: 14px; margin-bottom: 8px; font-size: 14px; color: #E8E4DC; cursor: pointer; text-align: left; } + + .section-header { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; } + .search-input { background: rgba(255,255,255,0.05); border: 1px solid rgba(201,168,76,0.2); border-radius: 8px; padding: 8px 12px; color: #E8E4DC; font-size: 13px; width: 100%; outline: none; transition: border-color 0.2s; } + .search-input:focus { border-color: #C9A84C; } + + .cards-grid { display: flex; flex-direction: column; gap: 10px; } + .family-card { width: 100%; display: flex; flex-direction: column; gap: 12px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 16px; font-size: 14px; color: #E8E4DC; cursor: pointer; text-align: left; transition: all 0.2s; } + .family-card:hover { background: rgba(255,255,255,0.06); border-color: rgba(201,168,76,0.3); transform: translateY(-1px); } + .card-top { display: flex; justify-content: space-between; align-items: center; width: 100%; } + .card-bottom { display: flex; justify-content: space-between; align-items: center; width: 100%; font-size: 11px; color: #8A8478; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 10px; } + .card-title { font-weight: 600; font-size: 15px; } + .status-indicator { display: flex; align-items: center; gap: 4px; } + .status-indicator::before { content: ''; width: 6px; height: 6px; background: #2ECC71; border-radius: 50%; } + .action-hint { color: #C9A84C; opacity: 0; transition: opacity 0.2s; } + .family-card:hover .action-hint { opacity: 1; } + + .empty-state { text-align: center; color: #8A8478; font-size: 13px; padding: 20px 0; font-style: italic; } + .role-badge { font-size: 10px; text-transform: uppercase; padding: 3px 8px; border-radius: 6px; background: rgba(255,255,255,0.1); color: #B8B2A6; } .role-badge.owner { background: rgba(201,168,76,0.15); color: #C9A84C; } .field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; } .field span { font-size: 12px; color: #B8B2A6; } .field input { background: rgba(255,255,255,0.05); border: 1px solid rgba(201,168,76,0.2); border-radius: 8px; padding: 10px 12px; color: #E8E4DC; font-size: 14px; width: 100%; } - .btn-primary { width: 100%; padding: 12px; border-radius: 8px; border: none; font-weight: 600; cursor: pointer; background: #C9A84C; color: #070A0D; } + .btn-primary { width: 100%; padding: 12px; border-radius: 8px; border: none; font-weight: 600; cursor: pointer; background: #C9A84C; color: #070A0D; transition: opacity 0.2s; } + .btn-primary:hover { opacity: 0.9; } .error-text { color: #EF4444; font-size: 12.5px; margin-bottom: 12px; } From 14b34ad5322554d4149638b24d71002684d74600 Mon Sep 17 00:00:00 2001 From: Wmj Ismail Date: Tue, 18 Aug 2026 13:12:05 +0800 Subject: [PATCH 2/3] docs: add architecture visuals and RAMZ tenant isolation decision Clio/Stripe competitive benchmark, moslem03/04 backend reality check, and the moslem04 sync API spec, plus the resolved schema-per-tenant isolation decision that unblocks Phase 2 of the sync spec. --- docs/architecture/benchmark-clio-stripe.html | 445 ++++++++++++++++++ .../integration-reality-check.html | 259 ++++++++++ .../ramz-tenant-isolation-decision.md | 58 +++ docs/architecture/sync-api-spec.html | 224 +++++++++ 4 files changed, 986 insertions(+) create mode 100644 docs/architecture/benchmark-clio-stripe.html create mode 100644 docs/architecture/integration-reality-check.html create mode 100644 docs/architecture/ramz-tenant-isolation-decision.md create mode 100644 docs/architecture/sync-api-spec.html diff --git a/docs/architecture/benchmark-clio-stripe.html b/docs/architecture/benchmark-clio-stripe.html new file mode 100644 index 0000000..35d705c --- /dev/null +++ b/docs/architecture/benchmark-clio-stripe.html @@ -0,0 +1,445 @@ +Falah OS — Competitive Benchmark: Clio & Stripe + + +
+
+ Falah OS — Nur Falah Admin Console +

Competitive benchmark: Clio & Stripe Dashboard

+

Two reference products mapped against EstateOS and the cross-tenant super-admin surface. Every row ends in a call: steal the pattern, build it ourselves, or note it as a structural gap the reference product simply doesn't resolve.

+
+ +
+
+
+

Clio

+ vs. EstateOS +
+

Legal practice management — case files, billing, client portal, co-counsel collaboration.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Feature areaWhat Clio doesVerdict for EstateOS
Intake → billingClio Grow captures leads via custom forms, hands off to Clio Manage automatically once a matter opens.Steal
Document automationTemplate library with variable-filled document generation, version control.Steal
Billing & invoicingTimers, hourly/contingency billing, AI-drafted invoices with error flagging.Steal
Trust accountingClient trust ledgers, deposit/disbursement tracking, compliance reporting.Steal
Client portalEncrypted messaging, document sharing, payments — but single-brand, one firm per instance.Steal UX
Faraid / Syariah workflowsNo inheritance-share calculation, no Syariah Court filing templates — generic common-law matter types only.Build
Panel-lawyer vetting"Clio for Co-Counsel" assumes counsel is already chosen — it's a collaboration portal, not a credentialing tool.Build
Per-tenant brandingClient portal is single-brand per firm — no white-label capability for a platform serving multiple firms.Build
Multi-tenant backendClio is one firm, one instance — no shared-backend, multi-firm architecture exists to compare against.Structural gap
RAMZ row-level accessFirm-scoped permissions throughout — no concept of row-level, cross-tenant gating.Structural gap
+
+
+ +
+
+
+

Stripe Dashboard

+ vs. Falah OS super-admin +
+

Connect platform admin — connected-account management, roles, audit, API access.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Feature areaWhat Stripe doesVerdict for Falah OS
Admin role tiersAdmin vs. Super Administrator — Super Admin gates who can grant/revoke roles and manage environments.Steal
Audit loggingActivity Logs API — exportable, SOC 2/PCI-ready record of key changes, invitations, role changes.Steal
API key scopingRestricted keys limited to specific permission sets per integration.Steal
Tenant status viewConnected-account list with onboarding and risk status at a glance.Steal UX
Onboarding flowHosted or embedded onboarding, incremental — collects only what's currently required.Steal
Cross-regime compliance routingNo concept of routing tenants through different regulatory rule sets (Syariah Court vs. JKPTG vs. govt case routing).Build
Usage / billing-trigger eventsActivity Logs are security-focused, not usage-metering. Stripe's actual metering product wasn't covered — separate follow-up needed.Build / unclear
Tenant = product lineEvery connected account is a roughly uniform merchant entity — no support for managing distinct SaaS products per tenant.Structural gap
RAMZ row-level accessIsolation happens at the account level (own object graph per tenant) — closer to separate-DB than row-level gating.Structural gap
+
+
+ +
+ Steal pattern transfers directly + Build reference has nothing comparable + Structural gap reference's whole model doesn't apply +
+ +
+
+

Combined — steal list

+
    +
  1. Intake → billing handoff (Clio Grow → Manage)
  2. +
  3. Document automation + template library
  4. +
  5. Trust ledger compliance reporting
  6. +
  7. Two-tier Admin / Super Admin roles
  8. +
  9. Exportable audit log, scoped API keys
  10. +
+
+
+
+

Combined — build list

+
    +
  1. Faraid inheritance calculation engine
  2. +
  3. Panel-lawyer vetting / credentialing
  4. +
  5. Per-tenant white-labeled portal branding
  6. +
  7. RAMZ row-level, shared-schema isolation
  8. +
  9. Cross-regime compliance routing (Syariah / JKPTG / govt)
  10. +
+
+
+ +
+ Sources: clio.com, docs.stripe.com, support.stripe.com — full citations in the prior research turns. + Open: Core Ledger billing-event readiness still unconfirmed against Stripe's metering product. +
+
diff --git a/docs/architecture/integration-reality-check.html b/docs/architecture/integration-reality-check.html new file mode 100644 index 0000000..0ffba5f --- /dev/null +++ b/docs/architecture/integration-reality-check.html @@ -0,0 +1,259 @@ +Falah OS — Backend Integration Reality Check + + +
+
+ Falah OS — checked against the actual repos +

moslem03 has no backend either — same gap as moslem04

+

Verified against nur-muslim-companion-v2 and nur-falah-prevention source. Neither app talks to a Falah OS server. There is no existing pattern to copy — the admin console link has to be built from zero.

+
+ +
+

01 · What moslem03 actually does today

+
+
+
+ moslem03.falahos.my + Svelte 5 PWA · static build +
+
→
+
+ Public third-party APIs + Al Quran Cloud · Aladhan · Overpass +
+
+
+
+ ✕ No auth + Anyone opens the app, no login +
+
+ ✕ No Falah OS server + Never calls anything on the Contabo VPS +
+
+ ✕ No admin console link + Nothing to sync — data never leaves the browser +
+
+
+ Same for moslem04 + Same localStorage-only pattern +
+
+
+ +
+

02 · What has to be built — bottom up

+
+
+
+
✓
+
+
+
casdoor (ummahid) identity service
+
Already running on the Contabo VPS :8000 — reuse it, don't rebuild login
+
+ Already exists +
+
+
+
1
+
+
+
Login screen in moslem04
+
App currently has zero auth UI — needs a casdoor sign-in flow bolted on
+
+ To build +
+
+
+
2
+
+
+
Sync API (new)
+
Push Faraid / Assets / Wassiyah / Hibah / Waqf drafts from localStorage to a server, encrypted
+
+ To build +
+
+
+
3
+
+
+
RAMZ tenant tagging
+
Every synced record gets stamped with which tenant/firm it belongs to
+
+ To build +
+
+
+
4
+
+
+
Admin console read view
+
Nur Falah Admin Console reads synced cases — view only, not edit
+
+ To build +
+
+
+
+ exists on the VPS today + needs to be built — nothing to copy from moslem03 +
+
+
+ +
+ Bottom line: moslem03 gave us one reusable piece — casdoor login. Everything else (sync, tenant tagging, admin visibility) starts from scratch. +
+
diff --git a/docs/architecture/ramz-tenant-isolation-decision.md b/docs/architecture/ramz-tenant-isolation-decision.md new file mode 100644 index 0000000..430e2ad --- /dev/null +++ b/docs/architecture/ramz-tenant-isolation-decision.md @@ -0,0 +1,58 @@ +# RAMZ Tenant Isolation — Decision + +**Status:** Resolved +**Blocks:** Phase 2 of the [moslem04 Sync API spec](./sync-api-spec.html) (admin console read view) +**Originally flagged in:** Nur Falah Admin Console PRD review + +## The question + +Falah OS runs one shared backend serving three tenants — EstateOS (law firm estate/probate, +Syariah Court), Tanah Hidup (waqf land lease, JKPTG), and Cair (govt case routing). How is +each tenant's data isolated from the others? + +Two options were on the table: + +1. **Separate database per tenant** — strongest isolation, highest ops overhead. +2. **Row-level RAMZ-gated access in a shared schema** — cheapest, weakest isolation story. + +## Decision + +**Schema-per-tenant on a single shared Postgres cluster.** + +Each tenant (EstateOS, Tanah Hidup, Cair) gets its own Postgres schema. Not a separate +server, not a shared table filtered by a `tenant_id` column. + +## Why + +- The three tenants aren't multiple firms sharing one product (the Clio model) — they're + three structurally different products, each answering to a different regulator + (Syariah Court, JKPTG, government agencies). A `WHERE tenant_id = ?` filter on a shared + table is not an answer a regulator's auditor will accept at face value; a schema + boundary is something that can actually be demonstrated. +- This was flagged as a trust/compliance blocker for the UK Innovator Visa pitch — the + isolation model isn't just an engineering choice, it's part of the pitch. +- Fully separate databases (or separate servers) solve the same trust problem but multiply + ops burden — separate backup policies, connection pools, migrations to run three times + instead of once. Not justified at current scale. +- Schema-per-tenant keeps one cluster, one backup policy, one connection pool — while still + giving each tenant a real, auditable namespace boundary. + +## What RAMZ still does + +RAMZ doesn't disappear — it moves down a level. Within each tenant's schema, RAMZ handles +the finer-grained access control that a schema boundary can't: which user or law firm +within EstateOS can see which case, which land officer within Tanah Hidup can see which +lease. RAMZ was never going to be strong enough to be the *only* thing standing between +tenants — now it doesn't have to be. + +## Consequence for the sync API + +The `tenant_id` field in the [sync spec](./sync-api-spec.html) becomes a schema selector, +not a row filter. The admin console's `/v1/admin/metadata` endpoint queries across schemas +explicitly, per-tenant — there is no accidental cross-tenant query path. + +## Still open + +This resolves the isolation *model*. It does not resolve whether Core Ledger already emits +the billing-trigger events Phase 2 needs, or who owns panel-lawyer vetting — both remain +open from the earlier PRD review. diff --git a/docs/architecture/sync-api-spec.html b/docs/architecture/sync-api-spec.html new file mode 100644 index 0000000..b5d06e2 --- /dev/null +++ b/docs/architecture/sync-api-spec.html @@ -0,0 +1,224 @@ +moslem04 Sync API — Architecture Spec + + +
+
+ Falah OS — Nur Falah Estate & Waqf Suite +

Sync API architecture — moslem04.falahos.my

+

A server-side sync layer for moslem04's six local-first modules, gated by casdoor login, encrypted client-side before it ever leaves the device, and surfacing only opt-in metadata to the admin console — never raw estate documents.

+
Out of scope: moslem03 (Muslim Companion) stays exactly as-is — free, zero signup, no account, no sync, forever. This spec touches nothing there.
+
+ +
+

01Data flow, end to end

+
+
+
+ moslem04 (browser) + localStorage stays the source of truth offline; sync is additive, not a replacement +
+
→
+
+ casdoor (existing) + OIDC login, issues JWT — nothing new to build here +
+
→
+
+ Sync API (new) + Accepts encrypted blobs, tags with tenant + user, never decrypts +
+
→
+
+ Postgres (new) + Ciphertext + RAMZ tenant column + a small plaintext metadata row +
+
→
+
+ Admin console + Reads metadata only — opt-in, read-only, per Phase 2 below +
+
+
+
+ +
+

02What gets encrypted vs. what admin can see

+

Estate plans are the most sensitive data a person will put into this app. The sync API is built so the server — and by extension the admin console — physically cannot read the contents of anyone's Faraid, Wassiyah, Hibah, or Waqf drafts. Encryption happens on-device before upload.

+
+ + + + + + + + + + +
FieldWho can read itNotes
payload_ciphertextUser onlyAES-GCM, key derived from casdoor session + on-device passphrase. Server stores bytes it cannot open.
moduleUser + adminWhich of the 6 modules the record belongs to — plaintext, needed for routing and counts only.
plan_completedUser + adminBoolean per module. Lets the console show "how many people finished their Wassiyah" without seeing a single will.
panel_lawyer_requestedUser + adminExplicit opt-in flag the user sets — the only trigger that surfaces a person to a panel lawyer at all.
tenant_id (RAMZ)Admin onlyWhich tenant/firm this record is scoped to — still pending the row-level isolation decision from the PRD review.
claims_payload (H2)Nobody, not syncedHorizon 2 Claims module is excluded from sync entirely until the Phase 0 legal gate in DEPLOY.md clears.
+
+
+ +
+

03Endpoints

+
+ + + + + + + + + + +
MethodPathPurpose
POST/v1/auth/exchangeTrade a casdoor OIDC token for a moslem04 session token
GET/v1/sync/pull?since=<ts>Fetch records changed since last sync — for restoring on a new device
POST/v1/sync/pushBatch upsert of encrypted blobs from the local device queue
DELETE/v1/sync/:record_idTombstone a record — deletes propagate across the user's own devices
POST/v1/consent/panel-lawyerThe one explicit opt-in action that makes a user visible to EstateOS admin at all
GET/v1/admin/metadataAdmin console read — returns only the plaintext metadata fields from §02, RAMZ-scoped
+
+
+ +
+

04Conflict handling

+

These are single-owner personal drafts, not shared documents — so last-write-wins by updated_at is sufficient for five of the six modules. Two exceptions: Family Waqf Designator can involve co-designators, so pushes there append to a change log instead of overwriting; Digital Claims (H2) doesn't sync at all, see §02.

+
+ +
+

05Rollout phases

+
+
+
+ Phase 0 + Auth + sync plumbing + Login, encrypted push/pull, cross-device restore. No admin visibility of any kind. +
+
+ Phase 1 + Opt-in metadata surface + Panel-lawyer request flag goes live. Admin sees only what §02 allows. +
+
+ Phase 2 · blocked + Full admin console read view + Waits on the RAMZ isolation decision and Core Ledger billing-event readiness — both still open from the earlier PRD review. +
+
+
+
+ +
+ Carried-over open questions this spec does not resolve: row-level (RAMZ) vs. separate-DB tenant isolation, whether Core Ledger already emits the billing-trigger events Phase 2 would need, and who owns panel-lawyer vetting once requests start arriving. All three block Phase 2, not Phase 0–1. +
+
From 4961a07356b46afb453ea7cf3485b637bd4e5c49 Mon Sep 17 00:00:00 2001 From: Wmj Ismail Date: Tue, 18 Aug 2026 15:55:17 +0800 Subject: [PATCH 3/3] docs: revise RAMZ isolation decision to database-per-tenant Schema-per-tenant was the original call, but falah-os-master's infrastructure/postgres/init.sql already establishes database-per-service as the platform convention (falahid, falahwallet, falahramz, falahmock, falahistore). Database-per-tenant matches that pattern and is a stronger isolation boundary than schemas would have been. --- .../ramz-tenant-isolation-decision.md | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/architecture/ramz-tenant-isolation-decision.md b/docs/architecture/ramz-tenant-isolation-decision.md index 430e2ad..224773f 100644 --- a/docs/architecture/ramz-tenant-isolation-decision.md +++ b/docs/architecture/ramz-tenant-isolation-decision.md @@ -1,6 +1,6 @@ # RAMZ Tenant Isolation — Decision -**Status:** Resolved +**Status:** Resolved (revised 2026-08-18 — see amendment below) **Blocks:** Phase 2 of the [moslem04 Sync API spec](./sync-api-spec.html) (admin console read view) **Originally flagged in:** Nur Falah Admin Console PRD review @@ -17,10 +17,19 @@ Two options were on the table: ## Decision -**Schema-per-tenant on a single shared Postgres cluster.** +**Database-per-tenant on the shared Postgres instance.** -Each tenant (EstateOS, Tanah Hidup, Cair) gets its own Postgres schema. Not a separate -server, not a shared table filtered by a `tenant_id` column. +Each tenant (EstateOS, Tanah Hidup, Cair) gets its own Postgres *database* — `falahestateos`, +`falahtanahhidup`, `falahcair` — not a schema, not a shared table filtered by a `tenant_id` +column. + +> **Amendment (2026-08-18):** this was originally decided as schema-per-tenant. Inspecting +> `falah-os-master/infrastructure/postgres/init.sql` showed the codebase already has an +> established convention — **database-per-service** (`falahid`, `falahwallet`, `falahramz`, +> `falahmock`, `falahistore`, all separate databases in one Postgres instance, not schemas). +> Database-per-tenant is a stronger isolation boundary than schema-per-tenant anyway, and it +> keeps one consistent pattern across the platform instead of introducing a second, competing +> one. Revised to match. ## Why @@ -31,25 +40,25 @@ server, not a shared table filtered by a `tenant_id` column. boundary is something that can actually be demonstrated. - This was flagged as a trust/compliance blocker for the UK Innovator Visa pitch — the isolation model isn't just an engineering choice, it's part of the pitch. -- Fully separate databases (or separate servers) solve the same trust problem but multiply - ops burden — separate backup policies, connection pools, migrations to run three times - instead of once. Not justified at current scale. -- Schema-per-tenant keeps one cluster, one backup policy, one connection pool — while still - giving each tenant a real, auditable namespace boundary. +- Separate databases on one Postgres instance is the platform's existing convention, not a + new ops burden — `falah-os-master` already runs 5 databases this way. Adding 3 more + follows the grain of the system instead of cutting against it. ## What RAMZ still does -RAMZ doesn't disappear — it moves down a level. Within each tenant's schema, RAMZ handles -the finer-grained access control that a schema boundary can't: which user or law firm +RAMZ doesn't disappear — it moves down a level. Within each tenant's database, RAMZ handles +the finer-grained access control that a database boundary can't: which user or law firm within EstateOS can see which case, which land officer within Tanah Hidup can see which lease. RAMZ was never going to be strong enough to be the *only* thing standing between tenants — now it doesn't have to be. ## Consequence for the sync API -The `tenant_id` field in the [sync spec](./sync-api-spec.html) becomes a schema selector, -not a row filter. The admin console's `/v1/admin/metadata` endpoint queries across schemas -explicitly, per-tenant — there is no accidental cross-tenant query path. +The `tenant_id` field in the [sync spec](./sync-api-spec.html) becomes a database selector +(one connection pool per tenant), not a row filter. The `moslem-admin` service's +`/v1/admin/metadata` endpoint holds one Postgres pool per tenant database — there is no +shared table and no accidental cross-tenant query path, because a query issued against one +pool cannot physically reach another tenant's rows. ## Still open