All app changelogs

EstateMax

business · Last updated May 21, 2026

Changelog — EstateMax

[2026-05-21] — Service fee & payout split, recurring "coming soon", delete fix

New: Service fee + payout split

You can now add a service fee to online payments and share it across up to 4 bank accounts.

Recurring autopay is now "Coming soon"

The card-on-file autopay module shows in Settings → Modules as Coming soon — tapping it says it's coming in a future version, it can't be switched on, and all autopay screens stay hidden until it ships.

Fixed: Delete (and other action buttons) did nothing

Several admin buttons — Delete resident, plus delete/reject/remove buttons on documents, vehicles, directory, security staff, meeting minutes, price list and the visitor log — silently did nothing. They passed a name into the button's click handler in a way that broke the handler whenever the name was present. All fixed. Deleting a resident now also wipes every record they own (gate codes, household passes, vehicles, charges, payments, complaints, poll votes, push subscriptions, group codes, etc.) so the database stays clean; gate-log and panic history are kept but anonymised.

Internal

[2026-05-20] — Separate House + Unit fields, and a Paystack safety net

House and Unit are now two fields

The single "House / Unit number" box is now two fields — House number (required) and Unit number (optional) — on the resident sign-up form and on the admin Add-resident and Edit-resident sheets. The two are stored separately and still shown as one combined line ("House 7, Flat 2B") everywhere else, so nothing else changes visually. Existing residents keep their current value; when you next edit one, the House field is pre-filled with it so you can split it cleanly.

Paystack payments are now bulletproof

A deep audit of the payment flow (resident pays a service charge → Paystack → charge marked paid) confirmed the happy path works correctly: amounts are handled in kobo, the payment is verified server-side against Paystack with your secret key, duplicates are ignored, and the charge flips to paid with the right balance maths. Two hardenings added:

Internal

[2026-05-20] — Marketing + app description refreshed for the full feature set

Internal

[2026-05-20] — Block codes on unpaid dues (grace period)

New opt-in debt gating: residents who owe service charges past a grace period can't generate any codes until they pay.

[2026-05-20] — Collect payments from a resident's profile + fixed levy icon

page now has a Collect payment button: it lists what they owe, and tapping a charge records a cash / transfer / POS payment that deducts from the balance (and marks the charge paid once settled). Their charge rows are now tappable too — tap any owing charge to record against it. An Other / one-off payment option handles ad-hoc amounts that aren't tied to a bill.

showed a blank icon because the category used an invalid icon name. They now show a building icon; light/connection fees show a clearer bolt icon.

Internal

Material name domain-add (not a Lucide icon) → now building-2; connection switched from bolt to zap.

rdRecordPay(), and a one-off rdCustomPayment() flow, reusing the existing service-charges?action=get, payments/record, and payments/record-custom endpoints. No API or schema changes.

[2026-05-20] — Blank-page fix (notices + payments), view-as-resident, keypad cleanup

[2026-05-20] — Resident portal polish + notices fix

[2026-05-20] — Admin interfaces for every enabled module

Some modules were switched on in Settings but had nowhere for the admin to actually use them. This release closes those gaps and makes every module reachable.

New: admin Documents page

The Documents module (bylaws / policies / contracts) had a resident reading page but no admin uploader. New Documents page lets the manager add, edit and delete documents, filter by type (Bylaws / Policies / Contracts / Other), and choose whether each is visible to all residents or owners-only. Residents continue to read them at More → Documents.

New: admin Registered-vehicles page

The License-plate module let residents register their cars and security verify them at the gate, but the admin couldn't see the registry. New Vehicles page lists every registered plate with the resident, unit and car description, is searchable, and lets the admin remove a plate (e.g. for a resident who moved out).

New: admin Directory page

The Resident-directory module is opt-in (residents list themselves from My account), but the admin had no oversight. New Directory page shows everyone active, who's currently Listed vs Hidden, is searchable and tab-filterable, shows the current display settings (phones / units / owners-only), and lets the admin add or remove any resident from the directory — e.g. force-hide someone who complained.

Businesses is now easy to find

The Businesses manager (where churches / hotels / pharmacies inside the estate get their own login to release visitors) existed but wasn't surfaced on the dashboard. It's now a primary Quick-link on the admin home whenever the Special-business module is on.

Dashboard quick-links are module-aware

The admin home screen used a fixed set of shortcuts. It now renders one tile per enabled module, split into Quick links (the everyday ones) and More tools (the rest), so estates that turn modules off see a cleaner screen and estates that turn them on can reach everything. New tiles: Businesses, Documents, Vehicles, Polls, Diesel, Price list, Reports, Activity log, SOS log.

Settings → Modules: "Manage" shortcut

Each enabled module card in Settings now shows a Manage → button next to its toggle, linking straight to that module's admin page (Documents, Vehicles, Businesses, Meetings, Polls, Complaints, Broadcasts, Service charges).

Internal

[2026-05-19] — PIN visibility removed (security)

Reversed the earlier-today decision to let admin see a resident's current PIN. The plain-PIN copy was a security hole — anyone with admin access could read every resident's PIN, and a leaked admin session would expose hundreds of credentials at once. Admin still has full control of resident sign-in, just without seeing the secret.

What changed on the resident-detail page
Force-change-on-next-login semantics (clarified)
Resident self-service

Residents can already change their own PIN at My Account → Change PIN ([views/public/my-account.php](apps/estatemax/views/public/my-account.php) → [views/public/change-pin.php](apps/estatemax/views/public/change-pin.php)). No new UI needed.

Data + write-site cleanup

[2026-05-19] — Admin approval flow + manual PIN visibility

Two related upgrades to the resident-management surface:

Self-registration: admin can now approve / reject from the UI

The API has supported residents/approve and residents/reject since the self-registration module shipped, but neither was reachable from the admin UI. Now:

PIN visibility + manual set (new)

Admin can now see a resident's current PIN at all times and set it to any value they choose — not just reset-to-default. Mirrors the DryCleanPro pin_plain pattern.

Write-site sync

Every place that writes pin_hash to a residents row now also writes pin_plain:

security_staff and businesses tables don't get pin_plain (yet) — only residents, since admin most often needs to re-share a resident's PIN. Easy to extend later if needed.

Demo seed

[seeds/demo.sql](apps/estatemax/seeds/demo.sql) now sets pin_plain = '1234' on every demo resident, so the Portal Access card has something to show as soon as you open a profile.

[2026-05-19] — App overview document + monetization roadmap

New top-level [Overview.md](Overview.md) is now the live spec for what EstateMax is, where the code lives, and what's planned next. It covers: audiences and surfaces (admin, resident, security, business, self-registrants, marketers), the full module map with default-on/off settings, the route table (admin + public + marketing), the data model summary, external integrations, and the marketing surface.

The biggest new section is §7 Monetization roadmap — five resident-and-visitor-side revenue lines designed to bypass estate-management politics:

Each has a build sketch, schema stub, pricing model, and partner shortlist. No code changes today — the doc is the deliverable. Next session picks a feature off the roadmap and starts building.

[2026-05-18] — Marketing: angle-specific proposals + shareable HTML landing pages

Affiliates now have six angle-specific sales proposals to pick from in the Marketplace Promote tab, plus a shareable HTML link per proposal that opens as a beautifully styled mobile-first landing page (no more pasting long raw markdown into WhatsApp).

New proposals (each WhatsApp-clean — UPPERCASE headers, no markdown tables in the body, dashes for bullets)
New: shareable HTML landing pages

A new public route at /em-proposal/{slug} renders any marketing .md file as a beautifully styled mobile-first HTML page using marked.js. Affiliates share THIS link (not the raw markdown) on WhatsApp — recipients open it on their phone and see a proper landing page with hero strip, cinematic typography, a CTA card, and the affiliate's name attributed via ?ref=.

Marketplace Promote tab upgrades

[2026-05-18] — Marketing sweep: dropped "no SMS bill" as a selling point

Property managers don't all run an SMS provider account, so leaning on "no SMS bill" / "no per-SMS cost" makes the pitch feel like it's targeting only managers who've already faced that bill. Reframed every SMS-bill hook across the marketing pack to talk about what the feature does (siren, push notification, in-app fan-out, instant alert) instead of what it doesn't cost.

Internal

No code, schema, or view changes — copy-only sweep inside apps/estatemax/marketing/.

[2026-05-18] — Recurring small groups (e.g. weekly prayer meetings)

A small follow-up to the group guest codes flow. Tucked away under a collapsed "Repeat this group" panel — most hosts won't notice it, but the ones who run a weekly prayer meeting or a monthly family hangout get something powerful:

1. When creating a group, the resident expands "Repeat this group", picks Every week or Every month, and creates as normal. 2. After the meeting's end-time passes, the cron tick mints a fresh batch of codes for the next instance — same names, same duration, new 6-digit codes. 3. The new codes carry a starts_at timestamp so they refuse to open the gate until next week's meeting actually begins. Safe to sit in the host's phone all week without abuse risk. 4. The host sees the upcoming group in their Your recent groups list with a "Repeats weekly/monthly" badge and a different icon. They can share the codes ahead of time (each card still has Copy and Share-to-WhatsApp) — the recipient gets a "Save the date for [event name]" share message instead of the regular invitation, so they know it's a calendar reminder, not a today-now visit. 5. Cancelling the recurring group revokes the current week's unused codes and stops the spawn.

Internal
Marketing
Tested

8/8 end-to-end functional tests pass: parent group created with weekly rule, cron spawns one child with 3 codes, all 3 child codes have starts_at in the future and preserved names from the parent, verify-code refuses a future-starts code at the gate, accepts the same code once starts_at passes, second cron pass is idempotent.

[2026-05-18] — Group guest codes (party / small-gathering flow)

A resident hosting 12 friends for a birthday no longer has to tap "Generate code" twelve times. New flow at More → Group codes:

1. Resident picks the count (2 – 25), optionally names each guest, picks a shared end-time (2h / 4h / 6h / 12h / 1 day), and taps Generate. 2. Server mints that many separate single-use guest codes in one transaction. Each code is a normal access_codes.code_type='guest' row — single-use, individually revokable, with its own row in the audit log — and points back to a new code_groups row that carries the shared label + end-time. 3. The resident sees a per-code share screen: each code as a card with a Copy button and a Share-to-WhatsApp button. The share text mentions the event name ("You are invited to Birthday party at Happy Land Estate, Tunde."). 4. Cancel the party? One tap revokes every unused code in the group at once. Codes that were already used at the gate stay logged.

This is intentionally distinct from event-mass codes (one code, many uses, for 400-person Sunday services). Group codes target small gatherings where each guest deserves their own single-use code with their own name on it.

New
Marketing
Tested

6/6 in-memory functional tests pass: group row created, 7 codes inserted under it, per-code names preserved, using one code leaves the others untouched, group revoke-all kills unused codes only (leaves used ones logged), list-groups stats accurate.

[2026-05-17] — Settings overhaul + sensible defaults

A pass through the admin Settings page so a brand-new estate gets a usable app out of the box and a busy manager can find the one thing they came for.

New module defaults

Fresh installs now ship with these modules already on:

And these off (admin opts in when ready):

SMS is now an opt-in module

EstateMax defaults to in-app notifications + Web Push (both free). Admin turns on the SMS channel module if they want gate-code SMS or code-used confirmations to also go out by SMS — and pays for the SMS budget at the platform level. When the module is off, send_estatemax_sms() no-ops cleanly and the SMS sub-settings (code-to-resident, code-used) are hidden from the Resident Access panel.

Other Settings defaults
Settings page UI
Internal

[2026-05-17] — Broadcasts are now in-app notifications (no SMS)

Broadcasts no longer send SMS. Every resident now has a notice inbox they see right inside the app — no per-message bill, no SMS budget, no waiting for cron to drain a queue.

How it works now
Removed
Internal

[2026-05-17] — Self-registration simplified · marketing kit refresh

Self-registration is now a one-page form. Resident enters name, phone, optional email, house number, type, and picks their own PIN — no email OTP step, no default PIN. Two new sub-settings on the Self-registration module:

Default behaviour: admin still approves each new sign-up (safer default). Flip the toggle any time.

This makes launch day trivial: turn on self-registration with auto-approve, share one link in the estate WhatsApp group, every resident signs themselves up and starts using it the same day. After the launch wave, switch auto-approve OFF so new arrivals wait in your approval queue.

Marketing kit refresh

[2026-05-17] — Tier 1 + Tier 2 feature expansion

The biggest single drop since launch. Twelve new modules behind the new Modules grid in Settings — turn on the ones your estate needs, leave the rest off. Nothing in the old experience changes unless you flip a switch.

New modules (Tier 1)
New modules (Tier 2)
Settings rewrite

The settings page now opens with a module grid. Each card shows the module's name, a one-line description, an on/off toggle, and (when enabled) a Configure shortcut to the right Advanced sub-section. Cards for not-yet-shipped modules are marked with their Phase tag so you can see what's coming.

The long collapsible list of preferences is still there — it's under Advanced settings below the modules grid. Modules without a dedicated Configure sub-section (everything in Tier 2) save right from the card toggle.

Navigation cleanup
Forgot PIN by email (Phase –1)

Foundation that shipped alongside the modules. Residents, security guards, and business logins can reset their own PIN: click Forgot PIN? on the sign-in page → enter the email on their account → type the 6-digit code we email them → pick a new PIN. The code expires in 15 minutes, is single-use, and is locked after 5 wrong tries. otp_tokens table backs every OTP flow with bcrypt-hashed codes and per-email throttling.

Pitch material (Phase 0)

marketing/wins-vs-competitors.md lists 17 concrete advantages over Gpera, Gate Africa, Residence.ng, Venco, and the rest of the Nigerian field — plus an honest gap list, a side-by-side feature matrix, an objection-handling section, and a copy-paste one-page pitch.

Internal

[2026-05-16]

[2026-05-15] — Unified "what is this code?" share text

When residents and businesses share gate codes via WhatsApp / SMS, the recipient now sees a full Nigerian-friendly message instead of just six bare digits. Same shape across every share surface in the app:

``` You are invited to visit Happy Land Estate, Aunt Adaeze.

This code is valid between Thu May 14 at 6:07 PM and Fri May 15 at 12:07 AM.

Passcode: 805205

Show these 6 digits at the gate.

Powered by gate.happyland.app ```

New helpers
Server-built share_text everywhere

The API now returns share_text on every endpoint that issues or lists a code, so the front-end never has to compose the message itself:

Front-end share buttons consume server text
Files

End-to-end test verified all 5 variants produce sensible text including the example case from the brief.

[2026-05-14] — Audit log: humanised + auto-prune

Humanised
Auto-prune
Files

End-to-end tests against an in-memory DB: 10 humanizer cases + 6 auto-prune scenarios — 16/16 pass.

[2026-05-14] (earlier)

[2026-05-13] — Mobile compactness, empty-state CTAs, comprehensive audit + reports

Mobile compactness
Empty-state CTAs
Comprehensive audit log
Reports (new admin view)
Files

New: views/reports.php, api/reports.php. Modified: schema.sql (audit_log + indexes), helpers.php (em_audit rewrite), api/audit.php (filters + CSV), views/audit.php (full rewrite), app.json (routes + nav), views/public/home.php (compactness), views/public/household.php (empty-state CTA), views/public/history.php, views/public/meetings.php, views/public/complaint.php.

[2026-05-13] — Three new toggleable modules: QR/barcode, business visitors, event-day mass codes

Every module is opt-in per estate via Settings → Special Access Modules. Estates that only want resident gate codes leave everything off and see no UI change. End-to-end functional + integration tests for the new code paths: 61/61 pass.

Module 1 — QR + 1D barcode (feature flag: barcode_scan)
Module 2 — Special-business visitors (feature flag: business_visitors)

For estates with a church, hotel, pharmacy, event centre or shop inside the gates. Solves the "I'm going to the hotel" cover-story problem.

Module 3 — Event-day mass codes (feature flag: event_mass_codes, requires business_visitors)

For churches running Sunday service, event centres hosting one-day events — one code, hundreds of guests, configurable window.

Audit log (universal)

Every new action calls em_audit():

All visible in /audit for admins to audit who came in, when, by which guard, via which business, and which phone — including failed phone-match attempts.

Schema additions (additive only — lazy migrator picks them up on existing installs)
Settings (admin-configurable)

New "Special Access Modules" collapsible section in Settings:

Files

New: api/businesses.php, views/businesses.php, views/business-detail.php, views/public/business.php. Modified: schema.sql, app.json, seeds/fresh.sql, seeds/demo.sql, helpers.php, api/portal.php, api/settings.php, views/public/login.php, views/public/_layout.php, views/public/security.php, views/public/code.php, views/public/guest-code.php, views/public/household.php, views/partials/settings-sections.php.

[2026-05-12] — Marketplace + affiliate copy refresh

Rewrote every piece of customer-facing marketing copy so it matches the real, gate-focused, Nigerian-context product instead of the generic placeholder that still said "run a residential estate end-to-end".

Fixes

[2026-05-12] — Migrator deep fix (platform-wide), Get Code subtitle

Platform — root-cause fix for "no such table: panic_events"

The lazy schema migrator had three compounding bugs that, together, left installs permanently stuck after any partial migration:

1. Unconditional hash stamping in _lazy_migrate_if_stale(). The function stamped the new schema hash after updater_apply_scope() returned, regardless of whether the apply had errors or skipped statements. So a migration that failed halfway through still stamped the hash, and every subsequent request saw $haveHash === $wantHash and skipped the retry — even after the migrator code itself was fixed. 2. No staleness self-check. The lazy migrator's only "is this DB stale?" signal was the schema hash. If the hash was wrongly stamped (per #1), the DB was permanently marked "up to date" even with missing tables. 3. updater_apply_scope() ran the whole schema as one big $db->exec($sql) call. A single failing statement (typically a partial UNIQUE INDEX that conflicts with pre-existing duplicate rows) would roll back the entire transaction — taking every new table in the same schema down with it.

Fixes:

End-to-end verified: an existing install with panic_events / dependents / diesel_log missing AND the new hash already stamped will self-heal on the next page request. Resident data, access codes (including any duplicates), and existing tables are preserved exactly as they were.

Polish

[2026-05-12] — Household passes, navigation polish, deeper bug fixes

Fixes
New
Polish
Internal

[2026-05-12] — Portal polish, PWA install, bug fixes

Fixes
Polish
New

[2026-05-12] — Security fixes, Nigerian polish, new features

Security
Reliability
Nigerian-market polish
New features
Internal

[2026-05-12] — Edit + Delete across every admin entity

Internal

[2026-05-11] — Standalone-apps pivot

Internal

[2026-05-11] — v1.1.0 (Phase 2)

Gate access — revoke + guest codes
Gate access — verifier improvements
Service charges — online payment
Settings additions
Internal

[2026-05-11] — v1.0.0

EstateMax is now a full app. The marquee surface is the one-tap gate-code flow; the rest of estate management sits behind it.

Resident-facing (no MyPancho account needed)
Security-facing
Admin-facing
Platform integration
Internal

[2026-05-11]

Internal

[2026-04-20]

Internal

[2026-04-18] — Renamed

All notable changes to this app are recorded here. Newest entries on top.

[0.1.0] — 2026-04-16