Release Notes — Jun 29, 2026 — dev → prod promotion (refresh-session UX + billing/discounts + broadcasts rich text)
membervu/membervu-saas!249 — MERGED), 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
| Surface | URL | Login to use |
|---|---|---|
| Org portal (tenant) | stg-rcme.membervu.com | Admin admin@rcme.membervu.com / Admin123!; Treasurer treasurer@demo.membervu.com / Treasurer123!; Comms Mgr comms@demo.membervu.com / Comms123! |
| Member view | stg-rcme.membervu.com | Member testmember@rcme.membervu.com / Member123!; Expired expired@demo.membervu.com / Expired123! |
| Super-admin portal | stg-admin.membervu.com | Super Admin superadmin@zeniark.com / SuperAdmin123! |
| Email inbox (MailHog) | stg-webmail.membervu.com | — |
- Session expiry is now visible: when your access token lapses you get a "session expired" modal + your in-progress form is kept (#223). This is expected — re-authenticate and your data is still there.
- Cross-subdomain sessions rely on session-cookie config (#468). If staging is configured for it, logging in on one tenant subdomain keeps you authenticated across tenant subdomains; if a session doesn't carry over, flag the environment config rather than the code.
- Login + registration rate limits still apply (a
429= throttled, not a failure). - "Simulate Payment" is gone from payment flows — use "Record Payment" for a manual/offline payment. The payment gateway is still inert in QA (records only, no live capture).
- Discounts now render as real lines. A 100%-discounted invoice is no longer hidden from listings (#495), and discount caps only count settled allocations (#498) — verify totals across detail/list, not just one screen.
- Confirmation emails are now durable. A successful dues payment that misses its immediate email is caught by a background reconcile job within ~10 min (#118) — when checking "did the email send", allow that window before calling it a miss.
- Plain event edits no longer email attendees (#472). Attendees are emailed only via an explicit resend. Editing event details silently should send nothing.
- 2 DB migrations ship — both cleanly additive nullable columns (no backfill, no destructive step). Deploy is
prisma migrate deployonly (see §Deploy).
🔐 Auth, session & security — #223, #468, #484, #337/#492, #360, #486, #482
| # | Change | What to verify on stage |
|---|---|---|
| #223 high-blast | Session-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). |
| #468 | Cross-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. |
| #484 | Purpose-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 / #492 | Tenant 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 security | Open-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 security | PII 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. |
| #482 | Cron-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
| # | Change | What to verify on stage |
|---|---|---|
| #149 | Multi-invoice payment initiation via wallet channels. | Select several outstanding invoices and initiate one payment across them; the allocation lands on each invoice correctly. |
| #259 / #260 | Invoice 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 money | Apply-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 / #476 | Invoice 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. |
| #489 | Soft 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). |
| #118 | Payment-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. |
| #480 | Custom date-range picker for statements + export. | On statements, pick a custom start/end date range → the list and export honor it. |
| #467 | Removed 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. |
| #500 | Write-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
| # | Change | What to verify on stage |
|---|---|---|
| #473 / #474 | Check-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. |
| #478 | Attendee CSV export adds Company, Job Title, ISO-UTC timestamp, Event Code. | Export the attendee list → those four columns are present and correctly populated. |
| #479 | Free-event registration confirmation email. | Register for a free event → a confirmation email arrives (previously only paid events emailed). |
| #496 | Admin 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. |
| #487 | requireApproval persists on event create + edit. | Set "requires approval" on create, and toggle on edit → the setting sticks after save/reload. |
| #475 | Single-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 security | Event-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 / #491 | Logged-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. |
| #472 | No 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
| # | Change | What to verify on stage |
|---|---|---|
| #488 | Rich-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
| # | Change | What to verify on stage |
|---|---|---|
| #452 | Platform finance wiring (super-admin). | On stg-admin.membervu.com, the platform finance views are wired and show tenant finance data correctly. |
| #493 | Admin Billing Overview uses the PHP peso (₱) symbol. | Billing Overview in the admin portal shows ₱, not $. |
| #222 | cursor: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. |
| #471 | Humanized membership status label in the member sidebar. | The member sidebar shows a readable status (e.g. "Active") rather than a raw enum. |
| #477 | Show/hide password toggle on login + reset-password forms. | The eye toggle reveals/hides the password on both the login and reset-password forms. |
| #277 | Backend entrypoint migrate-resolve (ops). | Ops-only: the backend container starts and applies migrations cleanly. No member-facing change. |
Suggested review order
- Session-expiry UX (#223) — modal + form-preservation + multi-tab coordination. Highest blast radius (touches every form).
- 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.
- Payment-confirmation email durability (#118) — successful payment always emails (allow the ~10-min reconcile window).
- Events — cancel flows + free-event email + CSV + no-auto-email-on-edit (#496/#479/#473/#478/#472).
- Broadcasts rich text (#488) — compose / preview / HTML-source / brand color.
- Auth/security — purpose-aware magic links (#484), open-redirect (#360), PII redaction (#486), tenant-host resolution (#337/#492).
- UI polish — cursor:pointer / password toggle / status label (#222/#477/#471).
- Skip: live gateway/webhook capture (inert in QA — records only). "Simulate Payment" is removed — use "Record Payment".
🚀 Deploy / migration notes (ops + reviewer gate)
prisma migrate deploy only; never reset/reseed on prod. Both are cleanly additive nullable columns (no backfill, no destructive step):
20260625000002_payment_confirmation_email_sent_at(#118) —Payment.confirmationEmailSentAtnullable timestamp (used by the reconcile job). Additive.20260626000000_add_magic_link_token_purpose(#484) —MagicLinkToken.purposenullable text;NULL= legacy, treated asLOGIN. Additive.
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.