Release Notes — Jun 19, 2026 — dev → prod promotion (Waitlist + Platform Check-in + hardening)

What this release is. This is the dev → prod promotion candidate (draft MR membervu/membervu-saas!200) — everything accumulated on dev since the last prod release, going out together. 142 commits · 165 files · 80 issues. Verify it on staging (https://stg-rcme.membervu.com; admin portal https://stg-admin.membervu.com), which builds from dev — staging is your stand-in for what prod will become. Grouped by area with a "What to verify" hint. Two items are new features (Waitlist, Platform event check-in) and two are new auth/security surfaces (refresh-token rotation, broadcast XSS escaping) — those carry the highest risk and come first.

🧭 New to MemberVu? Start here

MemberVu is a multi-tenant membership platform. There are two separate web apps — know which one you're in:

Recommended path for this release: (1) read the env + login box below; (2) test refresh-token sessions (§🔑) — highest-risk new auth surface; (3) test Platform Check-in (§🛰️) — new cross-tenant surface; (4) test Waitlist (§🎟️) on a full event; (5) then work the remaining sections top-to-bottom. Full module-by-module "what to re-review" map: Platform section and the campaign note (see footer).

⚙️ Before you start — environment & logins

SurfaceURLLogin to use
Org portal (tenant)stg-rcme.membervu.comAdmin admin@rcme.membervu.com / Admin123!; Member testmember@rcme.membervu.com / Member123!
Super-admin portalstg-admin.membervu.comSuper Admin superadmin@zeniark.com / SuperAdmin123!
Email inbox (MailHog)stg-webmail.membervu.com
Backend APIstg-api.membervu.com
Scope notes — read before testing.

🔑 Session & auth — refresh-token rotation (NEW auth surface) — #359

Before you test this — what it is. Until now a login lasted ~15 minutes, then you were logged out. This release adds a refresh token: the app silently gets a new short-lived access token in the background, so your session keeps working past 15 minutes. Each refresh rotates the token (issues a new one, kills the old one), and logging out / resetting your password revokes the token. This affects every logged-in user in both portals.
#ChangeWhat to verify on stage
#359Refresh-token rotation — new RefreshToken table (hashed tokens), rotation on use, revocation on logout / password-reset / role-change. Sessions survive past the 15-min access-token TTL.Session survives: log in (org portal), leave the tab idle > 15 min, then click around — you should stay logged in (no surprise logout). Logout revokes: log in on two tabs, log out on one → the other tab should lose access on its next action. Password reset revokes: reset your password → existing sessions are kicked. Repeat the "stays logged in" check in the admin portal too.
Security reviewer note. The replay/reuse protection is implemented (re-using an already-rotated token is rejected) and is covered by backend tests. Your gate is to confirm it behaves end-to-end: (a) the refresh cookie is HttpOnly + Secure; (b) a logout/reset actually invalidates a still-held refresh token; (c) sessions surviving past 15 min is the intended prod posture (prod previously hard-expired at 15 min).

🛰️ Platform event check-in — NEW feature (super-admin portal) — #374–#399, #402

Before you test this — what it is & where. Platform operators (super-admins) can now open any tenant's event and manage attendance without logging into that tenant's org portal. This is a deliberate tenant-boundary crossing, so the testing emphasis is isolation + audit.
Where: stg-admin.membervu.com (NOT the org portal). Login: superadmin@zeniark.com / SuperAdmin123!. Pre-req: at least one tenant event with registered attendees. This is brand-new — no prior test guide covers it; see the new Platform section.
#ChangeWhat to verify on stage (admin portal)
#376 / #377Tenant Detail → Events tab + Event Attendees page, gated by PLATFORM_SUPPORT.Open a tenant → Events tab → pick an event → the Attendees page lists that event's registrants (attendance status, type). Confirm you see attendance data — not billing/financial fields (those must be redacted for operators).
#374Platform-scoped read routes for events + attendance.The attendee list loads for the selected tenant only. Switch to a different tenant's event → its list shows its own attendees, never the first tenant's.
#375 / #378Check-in write actions — mark / undo / bulk — each writing an operator audit row.Mark an attendee checked-in → state updates; undo → reverts; select several → bulk check-in. After each, confirm an audit entry records who (operator) did what. A denied/forbidden action should return a 403, not log you out.
#388QR scan-to-check-in on the Attendees page.Use the QR scanner → scanning a registrant's ticket QR checks them in. (No camera? Confirm the scanner UI opens without error — #405 removed a broken prop.)
#397NO_SHOW status + reconcile core (DB migration adds the enum value).Mark a confirmed attendee as NO_SHOW → status shows NO_SHOW; it's excluded from checked-in counts; it appears correctly in attendance reports/exports.
#398 / #399 / #402CSV attendance reconciliation (both portals) + reconcile mode (manual + CSV + finalize).Upload an attendance CSV → preview matches rows to registrants (invalid/duplicate flagged) → commit. Finalize marks remaining confirmed-but-unscanned attendees as NO_SHOW. Verify the same reconcile flow on the org portal too (#399).
Isolation gate (do this with two tenants). Check in an attendee for tenant A. Confirm via the tenant A org portal (or DB) that only tenant A's record changed and tenant B's identical event is untouched. UI looking right is not enough — isolation lives in the backend tenantId filter.

🎟️ Waitlist — now ON by default (NEW feature) — #301–#308, #333–#335

Before you test this — behavior change. When an event is full, new registrations no longer fail — they go onto a waitlist. An admin can then approve (optionally overriding capacity) or decline waitlisted people. This is ON for all events by default. Pre-req: create or pick an event and fill it to capacity so the next registration hits the waitlist.
#ChangeWhat to verify on stage
#301 / #302Registration diverts to waitlist when full — member, member+guest, and public/anonymous paths.Fill an event. Then register as a member, as a member adding a guest, and as an anonymous public visitor → each should be placed on the waitlist (not rejected, not given a seat).
#305Admin waitlist queue — read endpoint + UI.As admin, open the event → the waitlist queue shows the people who got diverted, in order.
#306 / #307Approve (with capacity override) / Decline queue actions.Approve a waitlisted person → they get a confirmed seat (capacity override works even when full). Decline another → removed from queue. Approving someone already CHECKED_IN returns a 409 (guard).
#308Approved-guest claim link — approval email mints a valid claim token.Approve a brand-new guest from the waitlist → they get an email; the claim link works (resolves, lets them set up / confirm) and its host is the correct tenant origin (not a broken/wrong domain). Check the email in stg-webmail.membervu.com.
#333 / #334waitlistActive in the public event DTO + "Join Waitlist" CTA on the anonymous public event page.On a full event's public page (logged out), the CTA reads "Join Waitlist" instead of "Register".
#335Waitlisted guests do not receive the join-time setup email.A guest placed on the waitlist should not get the account-setup email at join time (only on approval). Check MailHog.

📅 Event management — edit-save & display fixes — #280, #311, #319–#331, #347

#FixWhat to verify on stage
#280 / #328Event edit round-tripmaxGuestsPerMember persists (#280, was the open dropped-payload bug); endsAt can now be cleared (nullable, DB migration) (#328).Edit an event → set the per-member guest limit and an end time → save → refresh → both persist. Then clear the end time → save → refresh → it stays empty (no revert).
#311Guest ticket / QR portal links use the correct tenant origin.A guest's ticket/QR link points at the tenant's own domain (not a wrong/broken host).
#319RSVP / invoice / waitlist emails render the tenant-local date.Event emails show dates in the tenant's timezone, not UTC/server time. Check MailHog.
#320–#322, #325Avatar save-on-change + SVG rejection + remove-photo (#320/#325); API validation errors surfaced on the registration form (#321); stray leading dash removed from phone display (#322).Registration form shows real validation errors; phone numbers don't show a leading dash; avatar upload rejects SVG and supports remove.
#326–#331Event-management batch — incl. a PUT-crash fix on event update.Editing and saving an event no longer 500s; the management screens behave.
#347Configured ticket types display on event detail.An event with ticket types shows them on its detail page.

👤 Member-account hardening — #380–#395, #404

#ChangeWhat to verify on stage
#380–#387Self-service account endpoints hardened (validation, atomicity); defensive URL render + account UX (#383/#384/#391).From a member's Account page, submit bad input (malformed email/phone, oversized field) → rejected with a clear error; valid edits save. Re-test on an existing member record — hardening can reject values that used to slip through.
#389–#395Member-admin: schema-route guards (#389), role allowlist (#390), custom-field validation (#392), atomic admin email change (#393), orphan prune (#394), role reconciliation (#395).As admin, change a member's email → it's atomic (no half-applied state); assign roles → only allowed roles accepted; custom fields validate.
#404Platform-user reset tokens aligned with the pw-reset- token scheme.A platform-user password reset link validates and works (admin portal).

🐛 Bug batch 2 — #361–#369

#AreaFix & what to verify
#361paymentsSimulate-payment honors the entered amount (not a default). Record a simulated payment with a custom amount → that amount lands.
#362prospectsInvitation email sent on "Mark as Invited". Mark a prospect invited → email arrives (MailHog), link host correct.
#363rbacBank-accounts view for admin/finance roles. Confirm those roles can see bank accounts. (See §Deploy for the manual-script caveat.)
#364notificationsEmail-preference toggles persist (fields aligned with backend). Toggle a notification preference → save → refresh → it sticks.
#365membership-types"Default" badge + "Set as Default" (DB migration). Set one type default, set a second → only one default survives per tenant.
#366prospectsAPPROVED members counted in the pipeline.
#367admin-membersLayout overflow fixed on member detail (narrow viewport).
#368guest-portalProfile card shows Guest type.
#369admin-nav"Check In" stays highlighted across its sub-routes.

🔧 Exports, broadcasts & RBAC seed — #228, #252, #348, #372, #405

#AreaChange & what to verify
#252broadcasts securityMerge-tag personalization + preview guard, with HTML-escaping of merge-tag values (XSS hardening). Send a broadcast where a merge value contains <script>/HTML → confirm it's escaped in the email, while a normal {{first_name}} still substitutes correctly (not double-escaped).
#228exportsNative .xlsx output on existing export endpoints. Export → file opens as a real Excel sheet with correct headers/data; the CSV-injection guard (#336/#340) still applies.
#348storage/imagesStore bare S3 key for avatars + presign images inline. Upload an avatar → it renders for anonymous viewers after refresh (not a broken/download link).
#372rbacRole seeding is now additive + startup backfill (no overwrite). Existing custom role/permission state is preserved on deploy.
#405admin-portalRemoved an invalid audio prop from the QR scanner (follow-up to #388) — scanner opens cleanly.

🖼️ Media / logo storage hardening — #406, #407, #410, #411, #409 (code-only)

#ChangeWhat to verify on stage
#406 / #407Store bare S3 key for tenant/invoice logos; canonicalize logoUrl to bare key on org-profile write (no backfill).Upload a tenant logo + invoice logo → refresh → renders correctly. Re-save the org profile → the stored value stays a bare key (a presigned URL isn't re-persisted). Verify existing (old full-URL) logos still render after deploy.
#410Guard external http(s):// avatar URLs from the redirect-service rewrite (regression fix on #348).A member whose avatar is an external URL → it renders untouched (not mangled by the image redirect).
#411Persist isAttendeeListPublic / registrationDeadline / registrationClosed / hosts on event create (save-gap).Create an event setting those flags → read it back from a fresh load → all persisted (previously dropped on create).
#409Align tenant-admin setup-token with the pw-reset- contract.Tenant-admin setup link validates and works.

🔐 Security — exports & broadcast — #336, #340, #252

#ChangeWhat to verify on stage
#336CSV / formula-injection neutralized across all exports.Export data containing a cell starting with =, +, -, or @ → the export neutralizes it (e.g. prefixed) so it won't execute as a formula in Excel. Confirm normal numbers/text are not mangled.
#340Guest CSV export moved server-side + FE injection guard.The guest export downloads from the server (not assembled in the browser) and is sanitized.
#252Broadcast merge-tag values HTML-escaped (see §Exports above).Covered in the broadcast row above.

🛠️ Backend / auth + Frontend / admin polish — #341, #342, #356, #346, #350, #351, #357, #358

#ChangeWhat to verify on stage
#341Transactional email links via buildTenantOrigin.Password-reset / invite / confirmation emails link to the correct tenant host. Assert the host, not just that the email arrived.
#342Same-day restriction removed from admin check-in.Admin can check attendees in regardless of the same-day constraint.
#356 / #357Platform-admin self-service password reset (endpoints + forgot/reset pages, admin portal).On stg-admin.membervu.com, use forgot-password → reset → log in with the new password. Confirm it doesn't bleed into org-portal sessions.
#346Deep-link returnUrl preserved through login.Hit a deep link while logged out → after login you land on the original URL.
#350 / #351"All Members" search debounced (#350); mobile viewport overflow fixed on invoices/payments/account/dashboard (#351).Member search doesn't fire on every keystroke; the listed pages don't overflow horizontally on a narrow/mobile viewport.
#358Attendance mutation errors surfaced (onError).A failed check-in/attendance action shows an error to the operator (doesn't fail silently).

Suggested review order

  1. New auth surface: refresh-token sessions (#359) — confirm sessions survive past 15 min and logout/reset revokes.
  2. New cross-tenant feature: Platform Check-in (#374–#402) — isolation (two tenants), operator audit, 403-not-logout, NO_SHOW + CSV reconcile.
  3. New feature: Waitlist (#301–#335) on a full event — divert → queue → approve/decline → claim link.
  4. Security: broadcast XSS escaping (#252), export injection neutralization (#336/#340).
  5. Event edit + membership-type default: round-trip (#280/#328), only-one-default (#365).
  6. Email correctness: tenant-origin hosts + tenant-local dates (#311/#319/#341/#362).
  7. Member account + media: hardening (#380–#395), logo/avatar render incl. existing images (#348/#406/#407/#410).
  8. Regression sweep: bug batch 2 (#361–#369), mobile (#351/#367), FE polish.
  9. Skip: live gateway/webhook flows (inert).

🚀 Deploy / migration notes (ops + reviewer gate)

Four additive migrations — deploy via prisma migrate deploy only; never reset/reseed on prod.
  1. make_event_endsat_nullable (#328) — drops NOT NULL on Event.endsAt.
  2. add_membership_type_is_default (#365) — adds column + partial unique index. ⚠️ Pre-flight required: if prod already has >1 default per tenant, the index creation fails. Run SELECT "tenantId", COUNT(*) FROM "MembershipType" WHERE "isDefault"=true GROUP BY "tenantId" HAVING COUNT(*)>1; — must return zero rows.
  3. add_no_show_status (#397) — adds NO_SHOW enum value. Irreversible in PG (enum values can't be dropped).
  4. add_refresh_token (#359) — new RefreshToken table; reversible by DROP TABLE. ⚠️ auth-service/package.json changed — the prod image rebuild must pick up the new dependency.
Manual script (#363) — do NOT run on prod without review: scripts/sync-bank-accounts-permission.ts is not auto-run and is not required (the permission also lands via the additive #372 backfill). It deletes+recreates system-role permission joins — only safe while no per-tenant system-role customisation exists in prod.

Source of truth: the membervu/membervu-pm tracker + draft MR membervu/membervu-saas!200. This page is the tester/reviewer-facing view; the full campaign note (focused test plan, deploy gate, module re-review matrix) is tester-files/03_EXECUTION_REPORTS/prod-release-2026-06-19/RELEASE_NOTES.md; the permanent archive entry is tester-files/07_RELEASE_NOTES/2026-06-19-release-notes.md. Verified present on dev on 2026-06-24. Gateway flows inert in all QA environments.