All app changelogs

ServicePro

marketplace · Last updated May 25, 2026

Service Pro — Changelog

User-facing changes are appended here on every functional edit. The marketplace app-details page reads this file directly.

[2026-05-25] — Vendors set their own prices + agent's preferred vendor

for every item on their new Prices screen; the platform adds a commission on top to get the price the customer pays. Leave an item blank if you don't offer it. Prices save as you type.

added on top of the vendor's price), set by the admin on the Services page or the Prices page. e.g. at 50%, a ₦1,000 item shows ₦1,500 to the customer; that ₦500 is the pool shared between the agent, marketer, manager and platform.

than one service picks a default vendor per service; a new order goes straight to that service's default vendor (no more "finding a vendor" search). The agent can switch vendor per order, and a manager can still reassign. Set your defaults on the new-order screen ("Make this my default vendor") or per service in your account's Services tab.

that vendor's own price list — what the agent quoted is what the customer pays.

Prices pages now pick a Service + a Vendor and edit that vendor's prices directly (managers only for vendors in their areas).

matter which vendor fulfils it.

tests and scans; diagnostic centres price and run them (blood tests, imaging, cardiac, and more). Appears automatically — no reinstall.

Washing → Drying → Pressing → Ready; an AC job goes Installing → Testing → Ready; a generator/plumbing job Diagnosing → Repairing → Testing; a diagnostic order Sample taken → Running tests → Results ready. Vendors and managers see the right step buttons for the service instead of dry-cleaning wording on every order.

on now shows its default-vendor control right inside the card (no save + refresh), and choosing a vendor opens a searchable list grouped by area — so you can find a vendor anywhere in the country by name or area instead of scrolling a giant dropdown. The new-order vendor picker is grouped and searchable the same way.

several live states (accepted, on the way, in progress, ready, completed, with one in dispute and one cancelled) — not just dry cleaning — so a new install looks like a real, busy marketplace across all five services.

Internal

agent_preferred_vendors table (the legacy single agents.preferred_vendor_id column is kept only as a last-resort fallback). New API actions: vendors/set-item-price, agents/set-preferred-vendor, pricelist/for-vendor, pricelist/vendors-for-service, templates/set-markup, templates/set-vendor-item-price, managers/vendor-set-item-price. Lead-submit routes directly to the agent's vendor (broadcast retired for the agent flow; kept dormant for manager reassign). Demo data now seeds vendor prices, agent preferred vendors, and a Medical Diagnostic vendor/agent with orders.

advance flow (vendor-order-detail), the orders/change-status validator + remit gate, and the manager status dropdown all build from each template's service_statuses (universal anchors + that service's middle stages + the has_return tail) instead of a hardcoded dry-cleaning list. Cross-service demo generator expanded to a full per-service status spread.

[2026-05-22] — Audit fixes & hardening

hand over / retire a manager — that account is now signed out on its very next action, instead of staying usable until they happened to log out.

is told to get prices set first instead of the order being booked at zero.

cancelled, the completion/cancellation time is now recorded properly so reports and history stay accurate.

switched off.

Internal

wording in the agent order screen.

[2026-05-21] — Item categories (collapsible) + shorter demo IDs

collapsible sections (e.g. Dry Cleaning → Tops, Bottoms, Suits & formal, Traditional, Dresses & skirts, Bedding & home) so you don't scroll past 50 items to find one. Tap a group header to collapse/expand it. Applies to the admin Prices page, the manager Prices page, and the vendor price list. Existing installs get categories filled in automatically (no reinstall).

(was SVP-20260521-0001), and demo orders are short D0001-style codes (was SVP-DEMO-20260521-0001-AB) — far easier to read and read out.

[2026-05-21] — Safer demo, manager handover, customer PIN reset & deactivate

the bulk-removal behind it. There's now no way to accidentally wipe data — to clean up, delete individual users, or reinstall for a fresh slate.

Replace → action: pick a successor and all their areas, agents, vendors, service assignments and open issues move to that person; the old manager is set inactive (kept for payout history, never deleted). Managers still can't be hard-deleted.

(from their own order) can reset a customer's PIN — it generates a new 4-digit code to read back to the customer.

(they can't sign in) or back on, from the order view.

newer-SQLite features, so it reliably fixes blank items / ₦0 prices on the admin Prices page (and everywhere) regardless of the host's SQLite version.

[2026-05-21] — "Areas", catalog repair, copy-on-new-area, shorter IDs, status fix

area) — the data and links are unchanged, just clearer wording for Nigeria.

blank-named items by an older seed, the Prices page now fixes the rows in place (names + prices) instead of leaving ₦0 — even when orders already reference those items.

prices from an existing area, so you only tweak instead of typing everything from scratch.

instead of SVP-20260521-0001 (9 characters shorter).

shows the confusing "Calling customer"; it now reads "{Vendor} on the way" (e.g. "Cleaner on the way") and the status you pick matches what the card shows.

[2026-05-21] — Per-service preferred vendor, manager pricing, price self-heal

services and no single vendor covers them all — so the preferred (fallback) vendor is now set per service, right on the Prices page. The Zones page no longer has a preferred-vendor column; it's just for adding/managing zones.

below them you set the preferred vendor for that service+zone and edit the item prices, all in one place.

manager app lets a manager set prices, switch services on/off, and choose the preferred vendor — but only for the zones they're assigned to.

existed, the Prices page showed empty items / ₦0. It now self-heals: opening Prices (or re-adding demo data) rebuilds the service catalog and fills in the per-zone prices automatically.

[2026-05-21] — Commission rework, "Marketer" rename, manager command centre, push

vendor is gone — vendors are no longer recruited by marketers. That share is reallocated: +3% to the platform and +2% to the zone manager (the zone manager now earns 9% of each order's platform share). Marketers still earn on the agents they bring.

(Links, logins and accounts are unchanged.)

shows the correct names for each share (the manager/agent rows were mislabelled and mentioned "dry cleaning"); the retired vendor-marketer row is hidden and the total adds up to 100%.

vendor on board" link with copy + share buttons; vendors who sign up through it are placed under that manager in their zones.

everything in their zones that needs attention (orders waiting for a vendor, still searching, or with an open issue), with All / Urgent / Finding filters. Tapping an order opens a full mobile view where the manager can call the vendor or customer, edit the order (status, vendor, customer contact, item quantities) or delete it.

urgent items even when the app is closed (Web Push is enabled out of the box).

picker (no more typing zone IDs), and the manager's territory label is built automatically from the zones chosen. Wording is unified on "Zone".

can tap to see the full list, instead of overflowing with every zone.

don't recruit vendors) and fixes the "Recruit someone" button so its description sits on its own line. The recruit page is shorter and generic — no SMS/dry-cleaning/"commission" wording — with a friendly "How it works" guide, a generic "where will they use it?" dropdown, and copy + share buttons on each invite link. The earnings page no longer mentions earning from vendors.

dropdowns (so they scale to many services and zones), and the on/off availability switch is right there — pick a zone, pick a service, edit the prices.

areas (Yaba, Surulere, Wuse) showed ₦0 for AC/generator/plumbing because their prices were never seeded into the new per-service price list. They're seeded now, and re-adding demo data backfills any zone that was missing them.

columns (GPS centre, radius/step/max metres, window, expansions) and now shows just what matters: name, preferred vendor, agents, vendors, active, and a Prices button. Adding a zone only needs a name — the map/matching settings are tucked under "Advanced" with sensible defaults.

when you've placed test orders. It now also cleans up orders, broadcasts and preferred-vendor links tied to demo accounts.

generator repair and plumbing (not just dry cleaning), so dashboards, the manager queue and price lists look realistic.

[2026-05-21] — Per-service pricing, vendor tools, plain-English copy

Prices page, pick a service (Dry Cleaning / AC Install / Generator Repair / Plumbing) and a region, then set that service's item prices. Previously every region shared one (dry-cleaning) list.

page controls whether a service is offered in a region — when off, it's hidden from vendors and customers there.

page now has a tab per service (filtered to what's offered in their region), each showing that service's prices. Copy fixed: "platform commission" (not "overhead"), and "you keep the green amount for yourself".

opens a Notifications page (recent activity on their jobs).

waiting to be started, a pulsing banner on their home screen prompts them to act — accepting isn't enough, they must move it forward.

correctly per service (a plumbing job no longer says "washing" or "picked up") and in simpler English. "Active pipeline" → "Your jobs", "at the door" wording removed, and the new-order/job flow uses service-appropriate verbs.

how-it-works note is shorter ("Customers pay you in full. At pickup you remit only the platform's share — you keep the rest."); completed-order rows are now a single line.

behind an "Advanced" section, so you only need a name and centre point.

Internal

offered). Helpers svp_zone_service_enabled / svp_zone_services_for_zone.

labels (branches on has_return, reads middle stages from service_statuses). Wired into vendor/agent order, notifications, and track views. svp_v2_status_meta() retained for callers without a template.

via the existing templates/set-item-price; legacy price_items UI retired.

svp_template_items_for_zone. New /vendor-notifications route + view.

[2026-05-20] — Vendor screen fixes: order tabs, earnings view, header centering

drifting slightly right when one side of the header was empty).

with live counts — so vendors can see their past orders, not just the active pipeline.

balance (which doesn’t fit vendors — they’re paid in cash on site), it shows what they’ve earned this month, today, and lifetime, plus a tappable list of completed orders with the amount kept. Removed the wordy explanation under the figure; the “how it works” note is now accurate: customers pay in full in cash on site, the vendor keeps their share immediately and only remits the platform share at pickup.

description now sit on separate lines (they were bunched), and the “Enabled” pill no longer floats off-screen on mobile.

“· LAGOS”).

Internal

title is centred regardless of side content.

+ counts; action buttons only render on active orders.

completed orders) instead of including _wallet-view.php.

added .svc-card__body wrapper.

[2026-05-20] — V2 design rolled out to vendor / promoter / manager

The new live-card design (teal accent, dark hero cards, Inter + JetBrains Mono) now covers every role’s app — not just agents.

hero, pipeline grid), active-orders pipeline, and zone price list. The online toggle, order-advance buttons, completion-photo sheet, and the alarm-style new-order overlay all work exactly as before.

earnings, network counts) and the recruit screen (invite code, share links, downline lists). Fixed the old “CLEANER” label on recruited vendors — now reads “VENDOR”.

tappable escalation queue (each ticket opens its order).

card, range filter, tappable ledger, and “show more” pagination as the agent wallet.

experiment — admin list pages now scroll sideways so you can scan many records at once instead of scrolling through cards.

Internal

manager; agents keep their standalone agent-wallet.php).

promoter-recruit.php, manager.php rewritten to the V2 shell while preserving all existing API calls + JS behaviour.

data-label JS was removed.

[2026-05-20] — Fixed account tab bar + mobile admin tables + commission wording

wasn’t telling the bottom bar which role it was for, so it fell back to the customer menu — agents saw a “Track” tab, lost their Orders / Customers / Wallet tabs, and the Home button pointed to the wrong page. The tab bar now matches the signed-in role on the account screen, so every tab links correctly again.

(agents, vendors, orders, zones, managers, promoters, payouts, reports, price list…) used to overflow / scroll sideways awkwardly. On phones each row now stacks into a tidy card with “Label: value” pairs, so nothing runs off the screen.

earn a <em>X</em>% <strong>commission</strong> on every order&rdquo; to remove any ambiguity about it being a cut, not a share of the whole order total.

Internal

(it was defaulting to customer).

horizontal-scroll to a stacked card layout; svpAdminLabelTables() copies each table&rsquo;s <thead> labels onto its <td>s as data-label (re-runs via MutationObserver for JS-rendered rows).

&ldquo;{pct}% commission&rdquo;.

[2026-05-20] &mdash; Wallet copy + commission detail + account tabs

&ldquo;Auto-pays out every Friday&rdquo; (dropped the Paystack / bank wording). The &ldquo;How this works&rdquo; note now reads plainly: &ldquo;You earn <em>X</em>% on every order you place — credited to your balance as soon as the customer pays&rdquo;, where <em>X</em> is pulled live from the commission settings instead of a hard-coded 30%.

order now links straight to that order, and shows its ticket number. The ledger starts at 25 entries with a <strong>Show more</strong> button, so a long earnings history doesn&rsquo;t become an endless scroll.

separate tabs (Details opens first), so an agent who offers many services doesn&rsquo;t have to scroll past all of them to reach their details. Room to add more later without lengthening the page.

Internal

ledger query joins orders for share_token + ticket_number; client-side &ldquo;show more&rdquo; pagination (page size 25, fetch cap 500).

Details/Services/Preferences tab switcher. The agent services save-bar only shows on the Services tab when there are unsaved changes.

[2026-05-20] &mdash; Dashboard polish + fixed order-detail crash + notifications page

order.** Tapping an order anywhere (dashboard, orders list, etc.) opens the track page, which was throwing a fatal &ldquo;undefined constant SVP_APP_ID&rdquo; error because it didn&rsquo;t load the auth helper. Order detail now opens correctly.

opens a Notifications page &mdash; a feed of recent activity on your orders (accepted, picked up, ready, delivered&hellip;), each tappable through to the order.

&ldquo;Capture a lead · under 60 seconds&rdquo; subtext so the &ldquo;Place an order&rdquo; label is large and prominent.

&ldquo;My customers&rdquo; and &ldquo;My wallet&rdquo; cards, the count / amount no longer runs into the label &mdash; it now sits on its own line with proper spacing.

Internal

feed derives from order_status_log for the agent&rsquo;s orders.

/ .quick__sub set to display:block; bell is now an anchor.

[2026-05-20] &mdash; Agent app redesigned: V2 live-card screens + teal accent

All five core agent screens were rebuilt pixel-for-pixel from the new &ldquo;ServicePro Agent — Main Screens&rdquo; design. Same dark live-card aesthetic as the place-order screen, now with a deep teal accent (#157f76) replacing the old greens, Inter + JetBrains Mono typography, and warm cream paper backgrounds.

perforated stat strip (orders today / profit today / this week) plus a next-payout line. Big embossed teal &ldquo;Place an order&rdquo; button, quick links to customers + wallet, and your active orders.

completed / cancelled bars), a segmented filter with counts, and richer order cards showing vendor, total, and your commission per order.

live search, and A→Z customer cards with zone tint, order count, and last-order time.

balance, &ldquo;pays Friday&rdquo; chip, lifetime earned vs paid-out, a 7/30/all range filter, the full activity ledger, and a how-it-works note.

&ldquo;services I offer&rdquo; picker, editable details (name, phone, PIN, home area), push-notification preference, and sign out.

glyph icons (the active tab shows its label). Applies to every role.

Internal

palette + reusable primitives (hero, card, section label, status pill, avatar, segmented control). Each agent screen reads it via readfile.

svp_format_money_compact() to helpers.php.

_wallet-view.php, which other roles still use).

[2026-05-20] &mdash; Service-aware forms, calmer dispatch, emerald accent

&ldquo;Pickup&rdquo; / &ldquo;Pickup address&rdquo; with a &ldquo;deliver back to the same address&rdquo; toggle. On-site services drop the pickup language entirely &mdash; AC Install says &ldquo;Installation address&rdquo;, Generator Repair and Plumbing say &ldquo;Service address&rdquo;, and none of them show a delivery toggle (there&rsquo;s nothing to return). The labels switch live the moment you change the service, and the live preview card&rsquo;s label follows too.

emerald, matching the rest of the app &mdash; the coral/red is gone.

dry cleaner / installer / plumber&hellip;&rdquo; and &ldquo;Dry cleaner assigned&rdquo; instead of the generic &ldquo;vendor&rdquo;.

a brief reassurance countdown and the order always lands on an assigned vendor &mdash; a live one if someone accepts, otherwise the zone&rsquo;s preferred (or a nearby fallback) vendor. The old &ldquo;Routed to manager / no vendor accepted / your card has not been charged&rdquo; screen is gone &mdash; it didn&rsquo;t match the pay-on-delivery model and agents should never see it.

Internal

set them per service (dry cleaning = pickup+return, others = on-site). Per-slug fallbacks cover un-migrated rows.

5&ndash;7s agent radar) with max_expansions = 0; auto-assign gained a final fallback to any active vendor offering the service (preferring the order&rsquo;s zone) so it only escalates when literally no vendor offers the service.

(zone × active service), not just dry cleaning.

manager_review now resolves to the same &ldquo;assigned&rdquo; outcome for the agent. Added a hard 5&ndash;7s finalize so the screen always resolves even if a poll is slow.

[2026-05-20] &mdash; New-order screen: V2 live order-card design

preview&rdquo; look. A dark preview card sits at the top and fills in live as the agent works &mdash; customer name, service, area, pickup, and best-time all update in real time so the agent can see the order taking shape before sending.

01 Customer, 02 Pickup, 03 Best time to call, 04 Notes &mdash; so the whole flow reads at a glance instead of one long scroll.

Afternoon 12 PM&ndash;4 PM, Evening 4 PM&ndash;8 PM, Anytime) instead of cramped chips.

preview card, and a coral accent on selected options and the success state. The &ldquo;Send order&rdquo; button stays pinned to the very bottom.

Internal

while preserving every existing handler (mode picker, region-grouped area sheet, customer search + pre-fill, delivery toggle, collapsible notes, price list, submit). Added a setCell() preview-sync layer wired into the service, customer, area, pickup, and best-time interactions.

selector was updated to match.

[2026-05-20] &mdash; Sheet picker, friendly broadcast, orders list rework

the entire account-page script (so tab clicks, service toggles, and sign-out all silently failed). One renamed variable later, agents can tap services to toggle, the Account-info tab switches as expected, and Save changes / Sign out fire correctly.

on the area chip now opens a beautiful bottom sheet with zones grouped under Lagos Island / Lagos Mainland / Abuja / Other areas, plus a live search input. This scales cleanly to 50&ndash;100 zones &mdash; agents won&rsquo;t be hunting through one giant list.

and &ldquo;Find by name or phone&rdquo; no longer collide with the bold title on the New/Existing customer cards &mdash; title sits on its own line, description below.

randomised &ldquo;12&ndash;28 nearby vendors alerted&rdquo; instead of a literal small count, the per-ring wait is clamped to a random 6&ndash;12 seconds (down from 30), and the expansion ceiling is now one ring instead of three &mdash; so the order lands on a vendor within ~15 seconds whether or not anyone manually accepts.

accepts the broadcast or the order auto-drops to the zone&rsquo;s preferred vendor, the agent sees the same friendly &ldquo;Vendor assigned&rdquo; success card with the matched business name. The auto_assigned flag still ships through to the manager so they can see which orders required a fallback.

with count badges (Active 3, Done 12, Cancelled 1, All 16). Every order row now has an avatar, ticket pill, vendor + amount on the same card, a coloured status chip (service-aware: &ldquo;Dry cleaner accepted&rdquo;, &ldquo;Plumber assigned&rdquo;), and earned-amount callout when commission has landed. Tapping any row drills into the order detail (/track?token=&hellip;) instead of doing nothing.

Internal

max_expansions is clamped to 1. No schema change required.

the eyebrow to &ldquo;Vendor assigned&rdquo;, and caches the inflated nearby-count per order in sessionStorage so the number stays stable across polls.

name, and template vendor label; status pill colour + copy are derived from a per-status palette.

[2026-05-20] &mdash; New-order wizard rework: area chip + auto-prefill + softer notices

pickup section shows the current service area and defaults to the agent&rsquo;s home zone &mdash; one tap to change. Picking a zone here is what the order is bound to, so the agent never has to write &ldquo;Sangotedo&rdquo; or &ldquo;Lekki&rdquo; into the address field just to be detected.

customer from search, the saved pickup address and zone now drop straight into the form. They can still edit either before placing the order.

bar from this focused wizard and moved the dock to bottom: 14px + safe-area. The button no longer floats mid-page on short forms or tall phones.

&ldquo;Now / Later today / This evening / Tomorrow morning&rdquo; row with concrete slots: 9&ndash;12, 12&ndash;4, 4&ndash;8, Anytime. Each chip flexes equally to fill the row.

breathing room below it, so &ldquo;Customer&rdquo; no longer sits right on the new/existing cards.

&mdash; the &ldquo;where the dry cleaner goes&rdquo; clarification was removed.

toasts and .err boxes are gone. Warning prompts now sit on a soft amber surface with ink-on-cream text (the same warm palette used elsewhere). &ldquo;Show prices for this area&rdquo; without a zone fires a friendly floating notice instead of a screaming red toast.

Street, Admiralty Way, Badore Road, Akin Adesola, Allen Avenue, etc. Picking a returning customer now reliably pre-fills a credible pickup address.

Internal

as authoritative when the geocoder can&rsquo;t classify the address. The zone&rsquo;s center is used as the fallback lat/lng so broadcast + auto-assign still have a fix.

inline 📍 zone tag; the area chip + hidden zone_id is now the only source of truth for zone selection.

instead of saturated red.

[2026-05-20] &mdash; Place-order polish + editable account + track context

tight back-arrow + title row, body padding trimmed, section radius capped at 5px, and inputs shortened from 52px to 48px. Question marks removed from section headings (the &ldquo;Service&rdquo; / &ldquo;Customer&rdquo; eyebrow already labels the step). Customer-mode card descriptions trimmed to one tight line each.

&ldquo;Delivery is the same as pickup&rdquo; is on by default and the textarea only appears when toggled off. Best time to call became a chip picker (Now / Later today / This evening / Tomorrow morning / Anytime) so the agent never has to type. Notes hidden behind &ldquo;+ Add a note for the dry cleaner&rdquo; (or installer / plumber) &mdash; collapsible because most orders don&rsquo;t need them.

above the bottom tab bar instead of floating mid-page, with a safe-area inset so it doesn&rsquo;t collide with the iPhone home-indicator.

promoter / manager / customer) can now edit their name, phone, PIN, and address right on the Account screen without contacting platform support. Agents pick their home area from the zone dropdown. The PIN field stays blank by default &mdash; leave it empty to keep the current PIN, or type 4 digits to rotate.

opens an order by share-link, the page no longer shows the public &ldquo;Track your clothes / enter the phone number&hellip;&rdquo; prompt &mdash; it shows the ticket number, customer name, and a back arrow to the role&rsquo;s orders list. The timeline labels use the service-aware noun, so dry-cleaning orders say &ldquo;Dry cleaner accepted&rdquo; instead of &ldquo;Vendor accepted&rdquo;.

order detail when the matched vendor has a phone on file. Tap it to dial straight from the page.

a new order&rdquo;, customer rows with avatars + order-count pills, and a live filter input at the top. Empty state is now a friendly dashed card instead of plain text.

(--svp-radius-btn &amp; friends). Buttons everywhere look tighter and more consistent without per-view edits.

Internal

promoters, managers, customers) &mdash; validates uniqueness of phone within the role table, optional PIN rotation, audit-logs each update.

and feeds it through the status-friendly label map so every status string is service-aware.

app-user session so the next page render shows the new values.

[2026-05-21] &mdash; Wizard reordered + service-aware vendor labels + redesigned customer cards

The agent now picks what the customer is buying before talking about who the customer is. Single-service agents still skip Step 1 and the service appears as a compact chip.

or the long &ldquo;capture the customer&rsquo;s details&hellip;&rdquo; paragraph. The page now opens with one big line: Place an order. The explanatory text moved into the service-template description that surfaces under each service card in Step 1.

with proper Lucide-style SVG icons (user-plus for new, magnifier for existing), added a subtle vertical gradient on the card surface, a hover lift + soft shadow, and a chevron on the right that nudges forward on hover. Picked state uses an emerald-tinted gradient with a stronger shadow.

vendor_label column &mdash; the noun used to describe the service provider in agent-facing copy. Seeded values:

Agent-facing copy uses this dynamically &mdash; the &ldquo;Address&rdquo; section title now reads &ldquo;Where should the dry cleaner go?&rdquo; (or installer / technician / plumber) based on which service is picked. Switching services on the picker updates the noun live.

anchored at bottom: 16px &mdash; same as the tab bar &mdash; so on certain layouts (especially after the mobile keyboard closed from the customer-search input) it ended up overlapping the tab bar. It now anchors at bottom: 90px + env(safe-area-inset-bottom) so it always sits cleanly above the tab bar. Container is also pointer-events: none (button is pointer-events: auto) so it never blocks taps on whatever sits behind it.

Internal

column. Picked up automatically by the lazy migrator.

templates; a fallback map in agent-new-order.php covers existing installs whose vendor_label is still NULL until an admin sets it.

initial page load and any service-card click; [data-vendor-noun] spans across the form update in lockstep.

[2026-05-21] &mdash; New-vs-existing customer wizard + Save-services bug fix

/agent-settings page (now superseded by /account) had its &ldquo;Save services&rdquo; dock button positioned at the same bottom: 16px as the new shared tab bar &mdash; the button was rendering correctly, just hidden underneath the tab bar, so taps landed on the tabs instead. The /agent-settings and /vendor-settings routes now 302-redirect to /account, where the save bar sits at bottom: 86px (above the tab bar). Old bookmarks and the home-screen quick-action card keep working.

an order&rdquo; on the agent home opens a wizard where Step 1 asks &ldquo;Is this a new customer, or already on the platform?&rdquo; with two big cards:

first time. Step 2 captures name, phone, and a 4-digit PIN to share with them.

search field that hits the platform-wide customer base (not just this agent&rsquo;s contacts &mdash; the search uses customers/search which queries every customer row by name or phone). Results render as tappable cards; picking one fills in name + phone and skips the PIN field (they already have one). None of the other sections (service, pickup, notes) show until a mode is picked, so the wizard guides the user one step at a time.

pick the services you offer&rdquo; card is gone (services live on the Account tab now). The wide third quick-action is now &ldquo;Wallet &amp; profits&rdquo; pointing at the wallet page, which is what an agent actually checks several times a day.

Internal

The new-order page just calls it from the existing-customer step.

the visible new-details inputs when mode is new; pulls from the pickedCustomer JS object when mode is existing; never sends a PIN for existing customers (server-side orders/submit-lead already reuses the existing customer row when their phone matches).

.picked-card. Each section in the form carries a .mode-only--any / .mode-only--new / .mode-only--existing class that the JS toggles via display.

[2026-05-21] &mdash; Account tab + wizard polish + per-order detail from agent home

role gets an Account tab in its place. Tapping it opens a single unified /account screen with two top-of-page tabs: &ldquo;Services&rdquo; and &ldquo;Account info&rdquo;. Agents get an editable service picker; vendors see the same picker as read-only with a &ldquo;contact your manager&rdquo; note (and the manager&rsquo;s phone if assigned). Both roles see their phone, role, home/shop area and wallet balance under the Account-info tab, with a friendly note pointing to platform support for name / PIN / phone changes. The page ends with a red Sign out button (confirms first). The save bar only appears when the Services tab is open for an agent.

/agent-orders?order=N which dropped the user onto the full order list. Now drills into /track?token=&hellip; for that specific order &mdash; same rich detail view (status pill in plain English, items, addresses, timeline, completion photo).

gone.** The little zone tag now stays hidden until the address actually matches one of your zones (then it shows &ldquo;&#128205; Sangotedo&rdquo; etc.). Less noise on a clean form.

small uppercase eyebrow + intro paragraph), each section now leads with a step pill (Step 1 &middot; Service / Step 2 &middot; Customer / Step 3 &middot; Pickup &amp; delivery / Step 4 &middot; Notes) and a bigger plain-English title (&ldquo;What is this customer buying?&rdquo; / &ldquo;Who is the customer?&rdquo; / &ldquo;Where should the vendor go?&rdquo;). Inputs grew to 52 px tall with 1.5 px borders and a soft focus glow. Pickup-address + delivery-address + best-time-to-call now stack vertically (not the 2-column squeeze that broke alignment on phones). The customer-PIN field is a single wide centered field with 8 px letter-spacing &mdash; clean, no overlap. Service-picker cards have a 2 px border, a deeper picked state with a soft shadow, and bigger icons (52 px).

Internal

role &mdash; it reads $me['role'] to decide which sections to show.

from every role; account is appended on each.

list now pulls share_token so cards can link to /track?token=.

[2026-05-21] &mdash; Plainer English + card-based service picker + tracking page details

Round of polish driven by walk-throughs &mdash; the language was too jargon-y and the agent screens didn&rsquo;t use the words real Nigerian users speak.

What you&rsquo;ll see:

everywhere now. The home CTA is Place an order, the dashboard cards say Orders today / Profit today / Profit this week**, the empty state says &ldquo;No active orders. Tap Place an order above&rdquo;, and the new-order screen header is &ldquo;Place an order &middot; For your customer&rdquo;. Order-list rows say &ldquo;Customer paid &#8358;X&rdquo; instead of the previous &ldquo;Lead value&rdquo;.

Plain English throughout: &ldquo;Your profit is credited here when a customer pays the vendor for an order you placed. The system pays out every Friday.&rdquo; No more &ldquo;Paystack&rdquo; brand chatter in the UI &mdash; that&rsquo;s an implementation detail. Activity log shows friendly labels (Profit from an order / Friday payout / Wallet top-up / Refund) instead of the raw order_split / platform_share_remit keys.

pip detail, two mini-stats on the card itself (&ldquo;Profits credited / Paid out&rdquo;), activity rows render in a single bordered card with green-tinted icons for credits and brick-tinted icons for debits. Empty state has a friendly &ldquo;Nothing here yet&rdquo; card instead of a stranded one-liner.

/agent-new-order when an agent offers more than one service, Step 1 is a stack of big cards (icon + name + short blurb + green tick when picked). Pick a card &mdash; the rest of the form remains visible underneath. Single-service agents skip the picker.

offer&rdquo; (was &ldquo;What do you sell?&rdquo;), section subtitle is &ldquo;Pick the ones you want&rdquo;.

order on a customer&rsquo;s home opens a richer view: status pill in friendly English (&ldquo;Vendor is calling you&rdquo; / &ldquo;On the way back to you&rdquo;), pickup + delivery addresses, best time to call, full item list with quantities + line totals + grand total, timeline of every state change, and the completion photo if the vendor has marked it complete.

field next to phone with overlapping label on narrow phones. Now full-width with a clear label and tracked-out monospace digits.

Bug fix

status&rdquo; because both orders and customers have a status column and the WHERE clause didn&rsquo;t qualify which one. The agent-orders.php query now reads o.status everywhere.

Internal

agent-settings.php, track.php, _wallet-view.php.

on the tracking page + the refreshed wallet hero / activity styles.

[2026-05-21]

No desktop behaviour changed — every adjustment lives in @media (max-width: 720px) and @media (max-width: 480px) blocks in views/partials/_admin-head.php.

[2026-05-19] — Bug-fix pass: clickable everywhere, real order detail, vendor services managed by admin

Big polish pass after walk-throughs surfaced a stack of usability bugs.

What got fixed:

fire.** The JS namespace was renamed DCH &rarr; SVP during the rename, but the assignment line window.DCH = { ... } in [_head.php](views/partials/_head.php) was missed by the sweep, so every consumer that called SVP.post(...) got a runtime error. Same fix on the admin side (DCHAdmin &rarr; SVPAdmin).

meta[name=csrf] which doesn't exist &mdash; the partial emits meta[name=svp-csrf]. Swept across 7 admin views so all admin fetch calls actually attach the token.

Pulled the tab bar into [views/partials/_tabbar.php](views/partials/_tabbar.php) and every public dashboard includes it with its role + active key. Vendor tab bar always shows Home / Orders / Prices / Wallet / Services / Exit; agent always shows Home / Orders / Customers / Wallet / Services / Exit; customer / promoter / manager each get their own consistent set. The menu no longer changes when you navigate.

/vendor-orders now drills into a new [/vendor-order](views/public/vendor-order-detail.php) screen showing the customer (with tap-to-call), pickup + delivery addresses, best time to call, agent notes, the placing agent (also tap-to-call), itemised line totals + vendor / platform share split, payments table, full status timeline, and the next-step action button (Open pickup &middot; Start washing &middot; On the way &middot; Delivered &middot; Complete with photo). The in-card "advance" buttons still work; the card click drills into the detail.

picker let any vendor self-promote into any service &mdash; that's not how the platform works. The screen at [/vendor-settings](views/public/vendor-settings.php) now shows the vendor's current line-up, the services they don't have, and a pointer to their manager (with the manager's phone if assigned) plus platform support. The vendor self-service API (vendors/services-set) now returns 403.

New "Edit" button per row opens a modal with checkboxes for every active service template. Save calls the new vendors/admin-services-set API endpoint, audit-logged.

Visual polish:

Fraunces is a variable Google Font with optical sizing &mdash; modern, warm, less stylised than the heavy italic look the old font gave. Used as --svp-font-serif for every hero / greeting / page title across the app, dropped 36 explicit font-style: italic declarations.

Internal

shared _head.php and _admin-head.php partials.

pointing the vendor at their manager. The admin path is vendors/admin-services-set (require_auth-gated, audited).

registered in app.json. Vendor order cards carry a data-detail-href attribute so the card click navigates; in-card buttons stop propagation so they still do their own thing.

[2026-05-19] — Sign-in page redesigned: less squashed, more spacious

The login screen looked compact and pinched in the middle because every element &mdash; logo, greeting, phone input, button, signup strip &mdash; stacked back-to-back with the same 16&ndash;20 px gap, leaving big emptyish space below and nothing pinned to the bottom.

The new layout is a full-height flex column with a flex:1 spacer:

big title + supporting copy), then 36 px to the phone input + CTA.

doesn't shift up.

pinned to the bottom on the phone step, or the keypad + footer actions pinned to the bottom on the PIN step.

The PIN-step hero now uses the editorial Instrument Serif italic at 42 px (was 36 px in plain UI font), and the avatar grew to 72 px with a subtle emerald drop-shadow. Keypad keys are taller (76 px), text is larger (30 px digits), and inter-key gap loosened to 14 px. The signup strip cards each carry a small uppercase role label ("AGENT" / "VENDOR" / "PROMOTER") above the action description so the choices read instantly.

Same two-step JS logic (phone &rarr; check-phone &rarr; PIN &rarr; login &rarr; redirect) &mdash; only the markup + CSS changed.

[2026-05-19] — Fuller demo seed + logins moved to the top of app-overview

Two small but meaningful tweaks for testing:

7-8 orders in their history; every demo agent has 7-15 orders attached; every demo vendor has 6-12 orders attached. The mix is weighted toward completed (50 orders spread across the past 14 days) with one in-flight order at each pipeline stage and a handful of manager-review / cancelled cases. Wallet ledger, status history, and payments populate proportionally — every dashboard now looks lived-in.

the auto-assign fallback always has a target. The legacy zones.preferred_vendor_id column gets the same value as a safety net.

sits at the very top of the doc (after the title), with a "Quick start" block showing one phone+PIN per role for instant testing. The old "Demo data" section further down is now a short pointer back to the top.

To refresh an existing dev DB to the new seed, open admin Settings, click Remove demo data, then Re-add demo data. Production installs are unaffected.

Internal

spread; new preferred_vendors seed loop after customers are created.

"Sparkle Dry Vendors" &rarr; "Sparkle Dry Cleaners".

[2026-05-19] — Admin can impersonate any app-user

Adds a "View as" feature: an admin can click a button on any app-user row and open a new tab where they're signed in as that user, for debugging or customer-service work. The admin's own Pancho session in the original tab is undisturbed — only a second session entry is created under $_SESSION['app_users']['servicepro'].

Where it shows up:

row &rarr; opens /p/{uuid}/servicepro/agent as that agent.

customer row impersonates the customer of that order. (Customers don't have a standalone admin list page, so the entry point is the order detail.)

When viewing as a user, a sticky purple banner appears across the top of their dashboard: "Viewing as {name}" with a Stop button. Tapping Stop ends the impersonation and bounces back to the matching admin list page (/app/servicepro/agents, etc.). The audit log captures every start/stop with the admin's UUID, role, and target name.

Internal

(agents, vendors, customers, promoters, managers):

app_user_login(), stashes _impersonating_from + _impersonating_from_name + any pre-existing _impersonating_previous onto the new app-user session, audits, returns the redirect URL.

marker (since only the gated endpoint can have set it), restores any previous app-user session, audits, returns the admin redirect URL.

because they're called from the impersonated user's session, which doesn't carry the admin CSRF token.

[views/partials/_impersonation-banner.php](views/partials/_impersonation-banner.php) rendered from all five public dashboards. Each dashboard sets $impersonateRole (the role's plural URL slug, e.g. agents) before including, so the partial knows which api/{role}/stop-impersonate endpoint the Stop button should hit.

(window.open('about:blank') synchronously before the async POST) so the new tab always opens; falls back to same-tab if the browser blocks the popup.

[references/prompts/impersonate-feature.md](../../references/prompts/impersonate-feature.md) for replication across other Pancho apps.

[2026-05-19] — Multi-service templates (Phase B / C / D / E)

The bigger sibling of the rename: Service Pro is now a real multi-service marketplace. Dry Cleaning becomes the first of several services, with the same end-to-end flow underneath every category.

What's new for each role:

screen (link on the home page). On the new-order screen, agents who offer more than one service see a service picker at the top; agents who offer one skip the picker.

new Vendor Settings screen (added to the tab bar as "Services"). Incoming-order alerts now only fire for services the vendor offers.

delivered orders: take a proof photo, pick a completion note from the service template's drop-down, optionally add a free-text note, tap "Mark complete". The photo is saved to uploads/servicepro/{owner-uuid}/order-completion/{order-id}/ and rendered on the admin order-detail page.

lists every service template with item count, status-count, order count, and a manager picker. Toggle templates active/inactive without affecting existing orders. Four templates ship: Dry Cleaning, AC Install, Generator Repair, Plumbing.

service badge so it's obvious which service each order belongs to.

Under the hood:

service_item_zone_prices, service_statuses, service_completion_options. Plus two linking tables (agent_services, vendor_services) and one override table (preferred_vendors keyed on zone × service).

completion_note, completion_photo_path.

column stays as deprecated back-compat).

doesn't offer the order's service never gets the alert.

then to the legacy zones.preferred_vendor_id, then escalates to a manager.

template's manager_id when set (Phase C); falls back to the zone manager otherwise.

scatters AC / Generator / Plumbing across some of them so the multi-service picker has something to show out of the box.

broadcast filter rejects a non-opted-in vendor and accepts an opted-in one.

Internal

svp_template_get, svp_template_items_for_zone, svp_template_statuses, svp_template_completion_options, svp_agent_service_ids, svp_vendor_service_ids, svp_agent_services_set, svp_vendor_services_set, svp_agent_default_template_id.

orders/complete-with-proof, templates/list, templates/get, templates/toggle-active, templates/set-manager, templates/update, templates/set-item-price, templates/set-zone-preferred-vendor.

query param and reads from the new service_item_zone_prices table (defaults to dry cleaning, template 1).

service_item_id on order_items.

catalogs and statuses and completion options from admin UI) is deferred — the seeded set covers the four shipping services.

[2026-05-19] — Renamed to Service Pro

This app was previously called DrycleanHub. As we prepare to broaden it into a multi-service marketplace (dry cleaning today; AC install, generator repair, plumbing, and more to follow), the app id, brand, file paths, and internal identifiers all rename. The dry-cleaning configuration becomes the first service template once the templates subsystem ships.

What changed:

ticket prefix DCH-SVP-.

helpers, UI copy. Cleaners-of-clothes are still cleaners; the role inside Service Pro is now a generic "vendor" because plumbers, AC installers, and generator technicians are not cleaners.

per-user database location follows — db/users/{uuid}/servicepro.db. Existing development databases under the old name are orphaned (this is a dev-only operation; production has not launched).

flow, platform-share remittance gate, auto-assign fallback, Nominatim geocoding, v2 commission split (agent 30 / agent-promoter 5 / vendor-promoter 5 / L1 override 3 / L2 override 1 / manager 7 / platform 49). Nothing about the order flow changes.

Internal

call sites), DCH_* constants → SVP_*, DCH. JS object → SVP., --dch- CSS vars + dch- CSS classes → --svp- / svp-, cleaner_idvendor_id, cleaner_payout_kobovendor_payout_kobo, preferred_cleaner_idpreferred_vendor_id, cleaner_contacted_atvendor_contacted_at, status value cleaner_contactedvendor_contacted, auth role string 'cleaner''vendor'. View files cleaner.phpvendor.php. Smoke test renamed from references/dch-e2e-smoke.phpreferences/svp-e2e-smoke.php.

merging the new multi-service Service Pro spec with the operational details (sample logins, zone list, file map, conventions) carried forward from the old DrycleanHub overview. Decisions log section captures what's locked from the Phase A → Service Pro pivot.

[2026-05-19] — v2: agents become pure lead generators

Major architectural shift. Agents no longer touch clothes or money — they capture customer details and submit a lead. The vendor's delivery person handles everything physical at the customer's home, collects payment there, and remits the platform share back to us before the order can advance.

What changes for each role:

agent type. Home page shows "Leads today / Commission today / This week" instead of revenue. The order wizard collapses to a single lead-capture form (customer name, phone, PIN, pickup + delivery address, best time to call, notes). No items, no payment screen. Agent wallet is now earnings-only — no "Pay platform" or "Add card" actions.

with steppers (priced from the customer's zone, not the vendor's), record the door payment (cash/transfer/POS/card), then a single CTA "Remit platform share" charges your saved Paystack card. The order cannot advance to washing until the share has cleared.

given to you verbally — sign in at /login with your phone + PIN to track the order. Door payments go straight to the vendor; in-app payment is now a fallback only.

/zones). When no vendor accepts the broadcast within the window, the order auto-assigns to that preferred vendor — the agent and customer never see the gap. Reassign auto-assigned orders from /orders with the new "auto" filter.

Other notable changes:

starts at lead_submitted and a server-side gate blocks picked_up -> received until platform_share_remitted = 1.

payout). Vendors are no longer in the split because they keep their share at the door. Defaults: agent 30%, agent-promoter 5%, vendor-promoter 5%, L1 override 3%, L2 override 1%, manager 7%, platform 49%.

geocode_cache table for 90 days. Falls back to substring zone match on failure.

blocks (pills, avatars, tab bar, keypad keys stay round).

Internal

kiosk_*, items_mode, delivery_type, origin_* paths from application code. The lazy migrator picks up the new columns (customer_pickup_*, customer_delivery_*, platform_share_*, auto_assigned*, vendor_contacted_at, zones.preferred_vendor_id, new geocode_cache table) on next request.

including auto_assigned, vendor_contacted, and remitted/non-remitted buckets. The split fires from a recorded platform-share remittance.

orders/pickup-update-items, payments/record-payment, payments/remit-platform-share, zones/set-preferred-vendor. The legacy agent-charge and items-tbd-update actions are gone.

[2026-05-19 · midnight] — Demo data flips from opt-in to opt-out

User feedback: a "Populate demo data" button is dangerous in production (someone could click it on a live tenant and inject fake users that look real). Flipped the model:

A new svp_bootstrap_demo_if_fresh() runs once at the end of [helpers.php](helpers.php). Guarded so it only fires when the app context is servicepro and neither demo_seeded nor demo_removed is set in settings. After it runs once, the demo_seeded=1 marker means every subsequent request short-circuits.

only removes rows that are definitely demo:

svp_demo_credentials() (no prefix matching — real users with similar prefixes are safe)

real users / orders still reference them

Real users and real orders are never touched.

sample login table.

smaller ochre "Re-add demo data" button (with a "do not click in production" warning in its confirm dialog).

it only re-adds when called with force=true. The Remove path clears demo_seeded and sets demo_removed; Re-add (force) does the reverse.

[2026-05-19 · late evening] — Demo data + phone fixes round 2

numbers (+2348001000001…) used a non-carrier prefix that was easy to mistype. New numbers use actual MTN / Glo / 9mobile prefixes:

PINs unchanged (1111 / 2222 / 3333 / 4444 / 5555). Full roster in [app-overview.md](app-overview.md) — login table at the top now also lists the local form and reminds the admin every input shape resolves to the same account.

if (existing_orders < 8) guard. Each click adds another batch of 18 orders across all 13 lifecycle statuses. Users (agents/vendors/...) stay idempotent via the phone UNIQUE constraint, so re-running won't duplicate them. Ticket numbers now include a 4-char random suffix (SVP-DEMO-20260519-0001-A3F2) so the UNIQUE constraint never collides.

format variants for every role resolve correctly: +2348031000001 / 2348031000001 / 08031000001 / 8031000001 / 0803-100-0001 / 0803 100 0001 → all match Bola Akinpelu (agent).

exposed in the admin UI because a misclick in production would wipe live data. The svp_reset_demo_data() function stays in [lib/demo-data.php](lib/demo-data.php) for CLI / test use only; no admin endpoint, no button.

[2026-05-19 · evening] — Bug fixes from live testing

now auto-attaches the platform's X-CSRF-Token header. A small wrapper in [_admin-head.php](views/partials/_admin-head.php) monkey-patches window.fetch() for same-origin non-GET requests so existing inline fetch(url, {method:'POST'…}) calls keep working without per-page edits. Also exposes DCHAdmin.post() / DCHAdmin.get() helpers if you prefer explicit. The CSRF token is rendered into a <meta name="svp-csrf"> tag by the partial.

svp_normalize_phone() previously broke when someone typed 2348001000002 (country code, no +) — it double-prepended 234 and the lookup failed. Now all five common forms map to the same E.164: +2348001000002 · 2348001000002 · 08001000002 · 8001000002 · 0800-100-0002. Verified with 9 input variants in the smoke test.

lookup: exact E.164 first, then a last-10-digits LIKE %tail fallback. Catches any edge case the normalizer missed. Matches DrycleanPro's approach.

"too rounded" on test. New scale in design tokens: --svp-radius-input: 12px, --svp-radius-btn: 14px (was full pill). Applied to login phone input, all signup pages, and the public .svp-btn class. Status pills, filter chips, avatars, and the tab bar keep their pill shape — those are visual chrome, not data-entry.

[2026-05-19] — Polish pass

marketplace: 8 agents, 8 vendors (mix online/offline), 4 promoters in a three-level chain, 3 managers across regions, 10 customers, 18 orders spanning every lifecycle status (broadcasting / accepted / washing / ready / delivered / completed / escalated / cancelled), with the 5 completed ones fully split-and-credited through the wallet ledger. Idempotent — running it twice doesn't duplicate. Demo PINs uniform per role (1111 / 2222 / 3333 / 4444 / 5555); full credential roster lives in [app-overview.md](app-overview.md). Source: [lib/demo-data.php](lib/demo-data.php).

Surulere, and Wuse (Abuja) on top of the 5 launch zones, with cloned price lists (+10% / +10% / +30% multipliers).

Service Pro (one word, capital D, capital H, the rest lowercase) — matches the design. App.json name, page titles, error messages, audit log header all updated.

the editorial italic. The post-PIN greeting keeps the italic — that's where the warmth belongs.

number to continue."**

to the login dispatcher (it does for general public routes), so the in-app $basePath was null when the view rendered — links became /signup-agent instead of /p/{uuid}/servicepro/signup-agent. Defensive code in [login.php](views/public/login.php) now reconstructs $basePath from $ownerUser['uuid'] + $appId using the same isset() ?: '' idiom DrycleanPro uses. No core changes.

Applied to Price list and Audit log filters.

500.00 (naira) instead of 50000 (kobo). JS converts back to kobo on save so backend storage is unchanged.

its plain-English name, description, and a % input. Live total counter recomputes as you type — green when 100%, red otherwise. Saving converts to basis points server-side. Same eight rule keys; backend untouched.

customer-facing strings (order singular/plural, item singular/plural, ticket prefix, all status labels). Values are written to settings.label_{key} and read by get_label() in the core.

instead of agent:1 / raw UUID. Platform admin UUIDs render as "Platform admin". Helper: svp_audit_actor_label().

broadcast instead of order.create_and_broadcast). 30+ actions mapped in svp_audit_action_label()`.

(e.g. pool ₦6,000 · platform ₦360 instead of the raw JSON). Helper: svp_audit_details_label().

Sign-ins / Manager / Cron / Settings.

fresh.sql). Admin can flip it to invite-only in Settings → Signups.

convention a future developer or AI needs to know

setup guide with the two-phone broadcast test

[marketing/Email.txt](marketing/Email.txt), [marketing/SMS.txt](marketing/SMS.txt) — copy-paste outreach templates

screenshots to drop here for the marketplace card

Upload list (manual FTP)

Only the changed files — never push db/:

[2026-05-19]

everything under /apps/ (per the catch-all rule), so the <link> to /apps/servicepro/views/partials/_design-tokens.css returned 403 and every admin page rendered unstyled. The two shared head partials ([views/partials/_admin-head.php](views/partials/_admin-head.php), [views/partials/_head.php](views/partials/_head.php)) and [views/public/login.php](views/public/login.php) now inline the design tokens via <style><?php readfile(...); ?></style>. CSS source file stays pure CSS for editor tooling.

([views/home.php](views/home.php)) and on the Settings page ([views/settings.php](views/settings.php)). One row per audience (Agent / Vendor / Promoter signup, daily sign-in, anonymous tracking, manifest), each with a one-tap Copy button. URLs come from app_public_url() so they switch to the custom domain automatically once one is verified.

[2026-05-18]

🎉 Initial build (Phases 1-13 from the implementation plan)

This is the very first cut of Service Pro — a Lagos-wide marketplace for dry cleaning. Five user roles (Customer, Agent, Vendor, Promoter, Manager) share one app, with the install owner sitting on top as platform admin.

#### Foundation (Phases 1-2)

apps/drycleanpro/. Per-user DB at db/users/{owner_uuid}/servicepro.db.

apps/servicepro/designs/{static,interactive}/. Locked the design system in [views/partials/_design-tokens.css](views/partials/_design-tokens.css): emerald primary #0e3b2e, cream bg #f7f3e9, ochre accent #d4a13c, brick alarm #c14a2c; Plus Jakarta Sans + Space Grotesk + Instrument Serif.

(the Uber-style per-vendor alert ledger), wallet_ledger, role tables (agents/vendors/promoters/managers/customers), commission_rules, web_push_subscriptions, support tickets.

30 price categories, 70 items priced in kobo (shirts, trousers, suits, native wear, dresses, bedding) with premium markups on Lekki and VI.

promoter 4%, vendor promoter 4%, L1 override 2%, L2 override 1%, agent manager 3%, vendor manager 3%, platform floor 5%.

#### Auth + role dashboards (Phase 3)

the LoginA design (phone entry → 4-dot PIN dot indicator → numeric keypad). Role is auto-detected by svp_resolve_phone() across all five user tables.

[vendor](views/public/signup-vendor.php), [promoter](views/public/signup-promoter.php) — geolocation captured in background, invite codes link to upline.

to register the service worker and store the browser's PushSubscription.

#### Agent place-order (Phase 4)

→ choose items vs items-to-be-determined → category-tabbed grid with steppers → sticky dock with running total → "Confirm & broadcast" CTA matched to the AgentOrderA design.

vendor" radar with pulsing rings, live countdown synced to broadcast_expires_at, lazy radius expansion + manager escalation triggered by the same poll.

#### The Uber-style centerpiece (Phase 5)

online vendors within radius via Haversine, inserts per-vendor target rows, fires Web Push to each. Race-safe svp_broadcast_accept() (atomic UPDATE WHERE status='awaiting_acceptance'). Lazy expansion grows the radius after the accept window; escalates to a zone manager after the configured max_expansions.

Web Audio two-tone siren (lifted pattern from EstateMax panic-banner), vibration, countdown timer, full-screen brick-red banner with animated pulse. Polls /api/portal/incoming-orders every 2.5 s while online.

agent's screen transitions to "Vendor found".

#### Order lifecycle (Phase 6)

cards with one-tap "next status" buttons (accepted → picked_up → … → delivered → completed). 9-status flow.

for TBD orders, lists every zone price-item with steppers so they count the actual bag and re-price live; locks items via items-tbd-update.

via share_token link. Real-time status timeline from order_status_log.

view of zone prices showing vendor share vs customer price.

#### Payments (Phase 7)

from EstateMax: initialize, verify, charge_authorization (stored card off-session), create_recipient, initiate_transfer, webhook signature verification, plus svp_wallet_record() for the append-only ledger that updates running balances on each role table.

next-stage gate for TBD orders)

failed transfers auto-reverse the wallet debit

for TBD orders the customer pays directly.

#### Commission split + Friday payouts (Phase 8)

reads commission_rules, walks the agent's and vendor's promoter chains for upline overrides (L1, L2), writes one wallet_ledger row per party inside a transaction, and is idempotent via orders.split_applied.

wallet_balance_kobo >= payout_min_kobo, debits their wallet, creates a payouts row, initiates a Paystack transfer. Per-week idempotency via payouts.week_label. On transfer failure: rolls back the debit and marks the payout failed.

table + manual sweep button + retry on individual failed payouts.

#### Promoter network (Phase 9)

buttons (agent/vendor/sub-promoter) with the promoter's own invite code baked into the link, copy-to-clipboard, and live downline tree (direct agents, direct vendors, sub-promoters).

#### Manager escalation (Phase 10)

escalated order with three actions:

manager whose zone_ids_json includes the order's zone.

#### Customer experience (Phase 11)

orders with TBD callouts, history, share-token links to each order's public track view.

CTA; gracefully degrades when keys aren't configured.

flows; vendor business name + agent name visible to the customer.

#### Admin tools (Phase 12)

The platform admin's /app/servicepro surface gets a full dashboard:

manager review, vendors online, payouts pending/failed.

filter by live/escalated/completed; detail page shows items, status log, every payment, and the full wallet_ledger fan-out per order.

accept window, max expansions.

customer_price per item, per zone.

[promoters](views/promoters.php), [managers](views/managers.php) — rosters with orders / gross / wallet columns; suspend/reactivate agents inline; create top-level promoters and managers.

Paystack key entry, signup gate toggles, commission rules editor with live total validation (must equal 10000 basis points).

vendors, broadcast accept rate.

details rendered inline.

#### Polish + cron (Phase 13)

payout sweep

attachments when the app is removed.

SVP.fmtPhone, SVP.toast, SVP.get/SVP.post so every role page gets the same surface.

"You're offline" page in the brand's serif italic.

the cheap-Android floor.

instead of getting auto-swept (the spec called for this; default on).

---

Verification

A full end-to-end smoke test at [references/svp-e2e-smoke.php](../../references/svp-e2e-smoke.php) creates a test promoter chain (L1 → L2), manager, agent (recruited by L2), vendor (recruited by L2 too, same zone), customer, and a 6-item order. It exercises:

1. Broadcast — 1 vendor targeted at 95m, won race-safely 2. 9-stage lifecycle — picked_up → received → washing → drying → pressing → ready → out_for_delivery → delivered → completed 3. Payment + split — ₦6000 pool distributed: vendor ₦4680 (78%), L2 ₦480 (4%+4% agent_promoter+vendor_promoter), L1 ₦120 (2% override), manager ₦360 (3%+3%), platform ₦360 (5% floor + unclaimed 1% upline_l2) — sums to ₦6000 exactly ✓ 4. Idempotency — re-running split returns already_split: true 5. Payout cron — without Paystack credentials, the vendor's debit is automatically rolled back and the payout is marked failed 6. Cron hookrun_servicepro_cron() reports stats

All 70 PHP files lint clean. Schema applies to a fresh in-memory SQLite without error. JSON in app.json parses cleanly.

What still needs admin configuration before go-live

1. Generate VAPID keys in /settings — required for Web Push (no push = no alarm when vendor app is backgrounded) 2. Add Paystack live keys in /settings — required for card charges AND outbound transfers 3. Connect Twilio (optional) — only needed for SMS notifications 4. Onboard at least one Manager per zone — escalations need a target 5. Run a two-phone test — agent on one, online vendor on the other, place an order, watch the alarm fire

Manual FTP upload list

When this lands on the live server:

The lazy migrator handles the schema on the first request after deploy. Per Pancho rules: never push db/, users/, or uploads/.