ghostguild-org/docs/LAUNCH_READINESS.md

95 lines
5.2 KiB
Markdown

# Launch Readiness
**Status as of 2026-04-18.** Target launch: before 2026-05-01.
Single source of truth for work that must happen before cutover. P0 blocks launch. P1 is strongly preferred but survivable. Completed items have been archived — see `~/.claude/projects/-Users-jennie-Sites-ghostguild-org/memory/project_launch_readiness_archive.md`. Post-launch backlog lives in `docs/TODO.md`.
---
## Current state
- Vitest: 577/577 passing.
- Helcim plan consolidation migration ran against prod 2026-04-18 (Monthly plan id `50302`, Annual plan id `50303`). All six paid-flow manual tests pass via tunnel.
- Remaining launch blockers: see lists below.
---
## P0 — Must fix before launch
None outstanding. Privacy/Terms pages shipped, duplicate-customer bug fixed, pre-deploy migrations run.
---
## P1 — Strongly preferred before launch
None outstanding.
---
## Deploy checklist
Pre-deploy migrations have all been run. What's left:
- [ ] Merge `feature/helcim-plan-consolidation` into `main`.
- [ ] Set `NUXT_HELCIM_MONTHLY_PLAN_ID=50302` in Netlify production env.
- [ ] Set `NUXT_HELCIM_ANNUAL_PLAN_ID=50303` in Netlify production env.
**Env vars required in production (reference):**
- `MONGODB_URI`
- `JWT_SECRET` (or `NUXT_JWT_SECRET` — the `NUXT_` variant wins)
- `RESEND_API_KEY`
- `HELCIM_API_TOKEN`
- `NUXT_HELCIM_MONTHLY_PLAN_ID`
- `NUXT_HELCIM_ANNUAL_PLAN_ID`
- `SLACK_BOT_TOKEN`
- `BASE_URL`
- `OIDC_COOKIE_SECRET`
- `NUXT_PUBLIC_HELCIM_PORTAL_URL`
---
## Manual browser tests still needed
Cannot be verified by Vitest. All require a real browser + real Helcim test card + real email.
- [ ] **Event ticket purchase with payment** (HelcimPay.js iframe; use cloudflared tunnel or ngrok HTTPS).
- [ ] **Pre-registrant invite → accept flow** with paid tier (exercises Helcim customer creation during acceptance).
- [ ] **Magic-link login** including 15-min expiry and jti burn on reuse.
- [ ] **Guest event signup** — four branches: new email + consent, new email without consent, existing guest, existing active member. Confirms cookie only sets for new/guest, and confirmation email appends `/login` link for real members.
- [ ] **Mobile responsive layout** — sidebar hides ≤1024px, nav works on phone.
- [ ] **`--text-dim` / `--text-faint` WCAG AA contrast check.**
---
## Bylaws decoupling — follow-ups (added 2026-04-18)
Context: bylaws are being amended to remove automatic termination for nonpayment. Membership status will be fully decoupled from payment status; failed payments trigger committee outreach, not status change. Copy + UI access gates already aligned in `useMemberStatus.js` and `account.vue` (2026-04-18). Server-side status gating shipped as B2 (see archive). The behavioral changes below remain.
Not blocking launch — the amendment hasn't passed yet, and the user-visible copy/UI is already consistent. Pick up once the amendment is ratified.
### B1. `cancel-subscription` flips status to `pending_payment`
- `server/api/members/cancel-subscription.post.js:31,48`
- When a member cancels their paid subscription, status is set to `pending_payment` and tier to `'0'`. Under the new model, cancelling a payment plan moves the member to the $0 tier — status should stay `active`.
- **Fix:** change `status: 'pending_payment'``status: 'active'` in both the `findByIdAndUpdate` payload (line 31) and the response (line 48). Comment at line 26 also needs updating ("(not cancelled) so member can re-subscribe" → reflect new framing).
- Add coverage in `tests/server/api/cancel-subscription.test.js` if it doesn't already exist.
### B3. Vestigial `pending_payment` status
- Once payment is fully decoupled, `pending_payment` no longer gates anything and is functionally equivalent to `active`. Consider removing it from the enum (`server/models/member.js:38`, `server/utils/schemas.js:299`) and treating new signups as `active` from the moment of account creation.
- Touches: signup flow (`helcim/customer.post.js:34`, `invite/accept.post.js:48`), admin filter UI (`app/pages/admin/members/index.vue:45,382,499,1145`, `[id].vue:69,286`), admin alerts (`server/utils/adminAlerts.js:22,100-116`, `server/models/adminAlertDismissal.js:6`), and a data migration to flip existing `pending_payment` rows to `active`.
- Larger refactor — break out into its own ticket once B1 lands.
### B4. Admin "Pending Payment" filter label (cosmetic)
- `app/pages/admin/members/index.vue:45,499`, `[id].vue:69` show `pending_payment` as "Pending Payment". If B3 removes the status entirely, this disappears too. If we keep `pending_payment` for now, rename in admin UI to "Payment setup incomplete" so admins also stop conflating it with membership state.
---
## Post-launch backlog
See `docs/TODO.md` for:
- Button minimum target size (WCAG AAA 2.5.5).
- `/oidc/interaction/[uid]` routing quirk.
- Admin layout migration from `guild-*` tokens to zine spec.
- Admin dashboard quick-action button contrast.
- Members table NAME column clipping.
- OWASP ASVS L1 Phase 4 (file-upload validation pipeline, granular RBAC, credential encryption).
- `tickets/available.get.js:115` `memberSavings` block reports `$0 saved` for inactive members — cosmetic; suppress comparison block when `!hasMemberAccess(member)` if it ever surfaces in UI.