ghostguild-org/docs/LAUNCH_READINESS.md
Jennie Robinson Faber 886c62e7b1 docs(launch): condense LAUNCH_READINESS and ignore prereg dump script
Collapse completed launch sections (receipts Phase 1, cadence UX,
contribution-amount manual tests) into one-liners; move them to the
archive memory. Move the three known post-launch gotchas to their own
subsection. Ignore the local one-off preregistration dump script.
2026-04-20 19:34:38 +01:00

7.9 KiB

Launch Readiness

Status as of 2026-04-20. Target launch: before 2026-05-01.

Single source of truth for work remaining before cutover. P0 blocks launch; P1 is strongly preferred but survivable. Completed items are 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 on main: 652/658 passing. 6 pre-existing failures in tests/server/api/helcim-payment.test.js — unrelated to launch-blocking work, flagged in the Deploy checklist.
  • All launch code is on local main: Helcim plan consolidation, contribution-amount redesign, cadence UX unification, and receipts Phase 1. Not pushed — site is not on Netlify yet.
  • Helcim plan consolidation migration ran against prod 2026-04-18 (Monthly plan id 50302, Annual plan id 50303).
  • Contribution-amount migration has NOT yet been run against prod.
  • Receipts Phase 1 code is shipped; remaining work is deploy-time only (see Deploy checklist).

P0 — Must fix before launch

None outstanding.


P1 — Strongly preferred before launch

None outstanding.


Deploy checklist

Applies when the site is connected to Netlify / production hosting. Nothing here is actionable until that connection exists; kept here so nothing gets forgotten at cutover.

  • Push local main to origin/main.
  • Run node scripts/migrate-contribution-amount.cjs --apply against prod Mongo BEFORE the new code serves traffic. Idempotent; dry-run on local counted 34 members. Requires MONGODB_URI in env. The script writes contributionAmount (Number) derived from existing contributionTier (String) on every Member doc; the old field is left intact for a window.
  • Set NUXT_HELCIM_MONTHLY_PLAN_ID=50302 in production env.
  • Set NUXT_HELCIM_ANNUAL_PLAN_ID=50303 in production env.
  • Decide on the 6 failing tests in tests/server/api/helcim-payment.test.js — either fix or consciously accept. Not launch-blocking, but pre-existing red tests tend to mask new regressions.
  • Run node scripts/reconcile-helcim-payments.mjs --apply against prod Mongo AFTER the new code serves traffic to backfill Payment records for pre-existing members. Idempotent (unique helcimTransactionId); safe to re-run as a nightly reconciliation job post-launch.
  • Helcim dashboard: disable the default payment-confirmation email for plans 50302 + 50303. We send our own CRA-safe confirmation via Resend (server/emails/paymentConfirmation.js) triggered from upsertPaymentFromHelcim; leaving Helcim's default on = duplicate emails.
  • Run one real test charge on staging via the cloudflared tunnel and verify (a) a Payment doc in Mongo with amount, paymentType, status: 'success', and (b) exactly one CRA-compliant confirmation email (charity name + "not an official donation receipt" disclaimer; no banned assertive phrasing).

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

None outstanding. All launch-blocking flows verified via local dev or cloudflared tunnel with real Helcim test card + real email (see archive for the full log). The one remaining browser verification is the staging test charge bundled into the Deploy checklist above.


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 contribution amount to 0. Under the new model, cancelling a payment plan moves the member to the $0 contribution — 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.

Known gotchas worth addressing post-launch

  • Subscription cache fed wrong field on CREATE. subscription.post.js and update-contribution.post.js read subscription.nextBillingDate from Helcim's CREATE response, but Helcim returns dateBilling. The lazy refresh in subscription.get.js masks this (handles both shapes), so next-charge rendering works — but the cache starts empty. Fix at the CREATE sites so the cache is correct from first write.
  • Admin edit does not sync Helcim recurringAmount. /admin/members/[id] PUT writes contributionAmount direct to Mongo by design. Admins must PATCH Helcim manually. Worth surfacing in admin UI or docs.
  • Cadence switch rejected on active subscriptions. update-contribution.post.js:184-189 refuses cadence changes mid-subscription; no UI toggle exists on /member/account. Adding cadence switch would require a Helcim subscription replacement flow, not a plain update.

Contribution-amount redesign — cosmetic cleanup (naming only, not behavior)

  • Rename admin members column header "Tier" → "Contribution" (app/pages/admin/members/index.vue:265).
  • Delete dead app/components/TierPicker.vue.
  • Update stale tier comment in app/composables/useMemberPayment.js:59.
  • Update error log message referencing "tier" in server/api/members/update-contribution.post.js:221.
  • Rename handleUpdateTier handler in app/pages/member/account.vue.