Release Notes — Jun 29, 2026 — dev → prod promotion (refresh-session UX + billing/discounts + broadcasts rich text)

What this release is. The dev → prod promotion (MR membervu/membervu-saas!249MERGED), everything accumulated on dev since the Jun 25 release (!224). 103 commits · 140 files · ~44 issues. Verify on staging (https://stg-rcme.membervu.com; admin portal https://stg-admin.membervu.com) and on the prod release build now that it has landed. No single headline feature — this is a broad quality + UX release: a real session-expiry experience (modal + form-preservation instead of silent logout), a billing/discounts wave (multi-invoice pay, discount controls, line-item editing), a broadcasts rich-text editor, an events sweep (cancel flows, CSV, free-event email), and several auth/security hardenings.

🧭 New to MemberVu? Start here

MemberVu is a multi-tenant membership platform with two web apps: the org portal (stg-rcme.membervu.com — admins + members of a club) and the super-admin portal (stg-admin.membervu.com, separate login — Zeniark platform operators).

There is no new top-level feature this release. The change most likely to surprise you is the session-expiry behavior (#223): when your access token expires you now get a "session expired" modal and any form you were filling is preserved, instead of being bounced to the login screen. Treat that as expected behavior, not a bug.

Recommended path: (1) read the env + login box; (2) test session-expiry UX (§🔐 #223) — high blast radius, touches every form; (3) test the billing/discounts wave (§💳) — money-affecting; (4) test broadcasts rich text (§📣 #488); (5) work the rest top-to-bottom.

⚙️ Before you start — environment & logins

SurfaceURLLogin to use
Org portal (tenant)stg-rcme.membervu.comAdmin admin@rcme.membervu.com / Admin123!; Treasurer treasurer@demo.membervu.com / Treasurer123!; Comms Mgr comms@demo.membervu.com / Comms123!
Member viewstg-rcme.membervu.comMember testmember@rcme.membervu.com / Member123!; Expired expired@demo.membervu.com / Expired123!
Super-admin portalstg-admin.membervu.comSuper Admin superadmin@zeniark.com / SuperAdmin123!
Email inbox (MailHog)stg-webmail.membervu.com
Scope notes — read before testing.

🔐 Auth, session & security — #223, #468, #484, #337/#492, #360, #486, #482

#ChangeWhat to verify on stage
#223 high-blastSession-expiry experience — session-expired modal + guard, multi-tab refresh coordination, and form-state preservation across auth loss (wired into 4 forms).Let an access token expire (or force it) while filling a form → a "session expired" modal appears (not a silent bounce to login); after re-auth the form data is preserved. With two tabs open, a refresh in one coordinates (no logout storm / no double-prompt).
#468Cross-subdomain refresh cookie — refresh token cookie is domain-tunable (set/clear parity).Log in on stg-rcme.membervu.com → session persists across reloads. If the env is configured for cross-subdomain, the session carries to other tenant subdomains; logout clears it everywhere.
#484Purpose-aware magic links — an ACTIVATION link reactivates an INACTIVE member; a LOGIN link does not.For an inactive member, an admin-issued activation/reissue link logs them in and flips them to ACTIVE. A plain self-service login magic link for an inactive member is still blocked. Legacy links keep working (treated as LOGIN).
#337 / #492Tenant resolves from host + cross-host fallback for guest password flows.The correct tenant/branding loads from the host. Guest forgot-password / reset-password works even on a cold load / split SPA-API host — no "Organization not found" 404.
#360 securityOpen-redirect fix — backslash paths rejected in the redirect param.A login/redirect URL with a \-based path (e.g. /\evil.com) is not followed off-site; only safe same-site redirects are honored.
#486 securityPII redaction — guest-registration responses RBAC-gated; invitedBy.email and check-in codes hidden from non-privileged callers.As a non-privileged user, guest-registration / invitation data does not leak inviter email or check-in codes. Privileged roles still see what they should.
#482Cron-secret gate/api/cron routes require the cron secret.Mostly ops: cron endpoints are not publicly callable without the secret. No member-facing change; confirm scheduled jobs still run on stage.

💳 Billing & payments — #149, #259/#260, #494/#495/#498, #469/#490/#476, #489, #118, #480, #467, #500

#ChangeWhat to verify on stage
#149Multi-invoice payment initiation via wallet channels.Select several outstanding invoices and initiate one payment across them; the allocation lands on each invoice correctly.
#259 / #260Invoice line-item editing + itemized CSV export + reminder emails + dues/event line items.Edit an invoice's line items (add/change) → total recomputes; CSV export is itemized; a reminder email reflects the line items.
#494 / #495 / #498 moneyApply-Discount control + discount line rendering (#494); 100%-discounted invoices no longer hidden from lists (#495); discount cap counts only settled allocations (#498).Apply a discount → a discount line shows and the balance drops. A fully-discounted (₱0) invoice still appears in the invoice list. Cross-check the total in detail and listing.
#469 / #490 / #476Invoice stays visible + keeps full balance after a manual payment is rejected (#469); payment rows show payment ref + gateway ref (#490); invoice/statement search by full name (#476).Reject a manual payment → the invoice doesn't disappear and the balance is restored. Payment rows display references. Search invoices by "First Last" finds the member.
#489Soft duplicate-invoice warning on create (warn-and-confirm).Create an invoice that duplicates an existing one → a warning asks you to confirm (it doesn't hard-block, but it doesn't silently double-create either).
#118Payment-confirmation email durability via a reconcile job.Complete a dues payment → a confirmation email arrives (check stg-webmail.membervu.com). Even if the immediate send is missed, the reconcile job sends it within ~10 min — no successful payment goes un-emailed.
#480Custom date-range picker for statements + export.On statements, pick a custom start/end date range → the list and export honor it.
#467Removed misleading "Skip" on bank-transfer; proof-optional submit relabeled.On a bank-transfer payment, there's no confusing "Skip"; submitting without a proof is clearly labeled as optional.
#500Write-path allocation parity — aggregates count only settled payment statuses.Allocations/balances reflect only settled payments; pending/rejected don't inflate paid totals.

🎟️ Events — #473/#474, #478, #479, #496, #487, #475, #229, #499/#491, #472

#ChangeWhat to verify on stage
#473 / #474Check-in CSV Attended=Y flips NO_SHOW → CHECKED_IN; manual check-in changes are audited.Import an attendance CSV with Attended=Y → matching registrants flip to checked-in; the change shows in the audit trail.
#478Attendee CSV export adds Company, Job Title, ISO-UTC timestamp, Event Code.Export the attendee list → those four columns are present and correctly populated.
#479Free-event registration confirmation email.Register for a free event → a confirmation email arrives (previously only paid events emailed).
#496Admin soft-cancel + Cancelled filter + member self-cancel.Admin cancels an event → it's marked Cancelled (filterable), not deleted. A member can cancel their own registration.
#487requireApproval persists on event create + edit.Set "requires approval" on create, and toggle on edit → the setting sticks after save/reload.
#475Single-word-title event 404 fixed; duplicate slugs disambiguated; empty slug rejected.An event titled with a single word opens its detail page (no 404). Two same-titled events resolve to distinct pages.
#229 securityEvent-registration notification bell + XSS / tenant-isolation (IDOR) coverage.New registrations raise an in-app notification; a registrant from one tenant is never visible to another (IDOR), and notification content can't inject script (XSS).
#499 / #491Logged-in members stay on the member event view (no bounce to public) during hydration (#499); My Tickets tab race guard (#491).As a member, open an event by slug → you land on the member view with no flash to the public page. After registering, the My Tickets tab shows the ticket without a race/blank.
#472No auto-email on plain event edit — attendees emailed only via explicit resend (+ audit).Edit an event's details and save → attendees get no email. Only the explicit resend action emails them, and it's audited.

📣 Broadcasts & communications — #488 + tenant brand colors

#ChangeWhat to verify on stage
#488Rich-text editor for HTML email compose (replaces the plain textarea); sandboxed-iframe preview + HTML-source mode.Compose a broadcast with the rich-text editor (bold, links, lists) → the preview renders it safely in a sandboxed frame; toggling HTML-source shows/edits the markup. Switching back from rich-text warns before a lossy conversion.
Tenant brand colors applied to the broadcast email wrapper.A sent/test broadcast uses the tenant's brand color in the email wrapper (check the rendered email in stg-webmail.membervu.com).

🖥️ Platform / admin & UI polish — #452, #277, #493, #222, #471, #477

#ChangeWhat to verify on stage
#452Platform finance wiring (super-admin).On stg-admin.membervu.com, the platform finance views are wired and show tenant finance data correctly.
#493Admin Billing Overview uses the PHP peso (₱) symbol.Billing Overview in the admin portal shows , not $.
#222cursor:pointer on all clickable surfaces + native <select> (WCAG); disabled keeps not-allowed.Hovering clickable cards/rows/selects shows a pointer cursor; disabled controls show not-allowed.
#471Humanized membership status label in the member sidebar.The member sidebar shows a readable status (e.g. "Active") rather than a raw enum.
#477Show/hide password toggle on login + reset-password forms.The eye toggle reveals/hides the password on both the login and reset-password forms.
#277Backend entrypoint migrate-resolve (ops).Ops-only: the backend container starts and applies migrations cleanly. No member-facing change.

Suggested review order

  1. Session-expiry UX (#223) — modal + form-preservation + multi-tab coordination. Highest blast radius (touches every form).
  2. Billing/discounts — discount lines + ₱0 invoice visibility + multi-invoice pay + line-item edit (#494/#495/#498/#149/#259/#260); cross-check totals across detail/list/export.
  3. Payment-confirmation email durability (#118) — successful payment always emails (allow the ~10-min reconcile window).
  4. Events — cancel flows + free-event email + CSV + no-auto-email-on-edit (#496/#479/#473/#478/#472).
  5. Broadcasts rich text (#488) — compose / preview / HTML-source / brand color.
  6. Auth/security — purpose-aware magic links (#484), open-redirect (#360), PII redaction (#486), tenant-host resolution (#337/#492).
  7. UI polish — cursor:pointer / password toggle / status label (#222/#477/#471).
  8. Skip: live gateway/webhook capture (inert in QA — records only). "Simulate Payment" is removed — use "Record Payment".

🚀 Deploy / migration notes (ops + reviewer gate)

Two migrations — deploy via prisma migrate deploy only; never reset/reseed on prod. Both are cleanly additive nullable columns (no backfill, no destructive step):
  1. 20260625000002_payment_confirmation_email_sent_at (#118) — Payment.confirmationEmailSentAt nullable timestamp (used by the reconcile job). Additive.
  2. 20260626000000_add_magic_link_token_purpose (#484) — MagicLinkToken.purpose nullable text; NULL = legacy, treated as LOGIN. Additive.
⚙️ Config to set on prod: SESSION_COOKIE_DOMAIN=.membervu.com, SESSION_COOKIE_SAMESITE=none, SESSION_COOKIE_SECURE=true for cross-subdomain refresh (#468/#223); SECURE is forced in production regardless. Confirm the cron secret is set so /api/cron jobs run (#482). A new payment-confirmation reconcile job auto-starts on backend boot (#118) — no env required.
✅ Merge conflict (1 file, resolved): frontend/admin-portal/src/components/PlatformWalkInForm.tsx — prod's !227 TS2367 fix vs dev's dead-check removal; resolved on the reconciliation branch by keeping prod's CI-proven version (merged cleanly, 0 conflicts remaining).
🔙 Rollback: redeploy prod from rollback/prod-pre-release-2026-06-29 (6c46665c) — code rollback only; both migrations are additive, do not reverse-migrate a live DB.

Source of truth: the membervu/membervu-pm tracker + MR membervu/membervu-saas!249 (merged), sourced from reconciliation branch release/dev-to-prod-2026-06-29. This page is the tester/reviewer-facing view; the full release note (deploy gate, config, rollback) is membervu-saas/docs/runbooks/2026-06-29-prod-release-dev-to-prod.md. Builds on the Jun 25, 2026 release. Verified present on dev on 2026-06-29. Gateway flows inert in all QA environments.