Skip to main content

QA Checklist — Run Before Declaring Work Complete

Purpose: Every agent must run through this checklist after completing implementation and before presenting work as done. This catches the 9 categories of issues that have repeatedly shipped without detection.

Updated: 2026-03-30


How to Use

After finishing your implementation:

  1. Switch mental mode: "I am now a senior QA engineer who finds bugs others miss"
  2. Run through each applicable section below
  3. Fix ALL issues found — critical, important, AND minor
  4. Never defer with "we can address this later"

1. Expected Output Compliance (if you touched customer-facing or tier-gated UI)

  • Identified all affected tiers — which tier+channel combos does this change affect?
  • Read the acceptance spec — checked knowledge/acceptance/<tier>.md for each affected tier
  • Spec exists for every affected touchpoint — if missing, flagged to founder before proceeding
  • Code matches spec — every "Should See" item is visible, every "Should NOT See" item is hidden
  • No tier bleed — features from higher tiers don't leak into lower tiers
  • Upgrade CTAs present — locked features show appropriate upgrade messaging per spec

Quick check: For each tier affected, open the admin dashboard as that tier and visually verify against the spec.


2. Database Queries

  • No select('*') or bare select() — grep your changed files for these patterns
  • Every query has .limit() or .range() — no unbounded result sets
  • No user input interpolated into .or() or .rpc() strings — use parameterized methods
  • Loop operations are batched — no await inside for loops for DB calls
  • Multi-step mutations are idempotent — use upsert, not insert; include dedup keys

Quick check: grep -n "select\('\*'\)\|\.select()\|\.select(\"" <your-files>


3. Security

  • No sensitive fields in client component props — admin_token, access_token, stripe_*, *_secret, *_api_key
  • Mutation endpoints have CSRF checkverifyCsrf(request) at top of POST/PATCH/PUT/DELETE
  • Auth matches route purpose — founder routes check FOUNDER_TOKEN, admin routes check resolveTokenOrHeaders
  • No hardcoded secrets — all API keys, tokens, passwords from env vars
  • Webhooks verify signatures — Stripe: constructEvent, Twilio: validateRequest

Quick check: grep -n "admin_token\|access_token\|pco_secret\|cal_api_key\|sk_live\|sk_test" <your-files>


4. Client/Server Boundary

  • Server-only files have import 'server-only' — any file importing supabase admin client
  • 'use client' components only import from *-shared.ts or tier-config.ts — never from *-queries.ts
  • No SUPABASE_SERVICE_ROLE_KEY referenced in client code

Quick check: grep -rn "from.*queries" <your-client-components> — should return nothing


5. SEO & Metadata (if you touched any page)

  • Page has alternates: { canonical: '...' } — full absolute URL
  • Title uses { absolute: '...' } for sub-brand pages — SermonWise, ShareWiseAI
  • openGraph.siteName set for sub-brand pages — not inheriting "ChurchWiseAI"
  • JSON-LD uses JSON.stringify() — never raw HTML interpolation
  • Sitemap entries match actual pages — no phantom URLs

Quick check: Search your page file for canonical, siteName, absolute


6. Content Accuracy (if you touched marketing copy)

  • Tool counts match canonical values — 39 total (Starter=12, Pro=35, Suite=39)
  • Agent count is 2 — Care + Coordinator (not 4)
  • Tradition count is 17 — theological lenses
  • Church count is 218K+ visible — always filter directory_visible=true; 261K total rows
  • Illustration count is 53K+ — not 30K, not 320K
  • Pricing matches PRICING.md — every dollar amount verified
  • No false social proof — no "join churches that switched" without real customers
  • "Coming Soon" products are gated — no active signup/checkout flows

Quick check: grep -rn "33 tool\|4 agent\|320K\|240K\|261K\|30,000\|Trusted by church" <your-files>


7. UI/UX (if you touched components)

  • Gold text NOT used on light backgrounds — use #8B6914 or #a07d1c instead of #D4AF37
  • Card pattern followedbg-white rounded-xl border border-stone-200 shadow-sm
  • Container is max-w-7xl — not max-w-6xl
  • Animations respect prefers-reduced-motion
  • Images have descriptive alt text

7a. UI Interaction Click-Through (MANDATORY for every UI flow) ⚠️

If code you changed touches ANY clickable element, this section is not optional. Code-path tests, type checks, and unit tests CANNOT catch interaction bugs. WebFetch cannot click buttons. Only actually clicking does.

Rule of thumb: If you haven't clicked the button, it isn't tested.

  • Every new or changed button has a Playwright click test asserting the side effect (navigation, tab switch, modal open, form submit, state update). Placed in e2e/{feature}-clickthrough.spec.ts
  • Every new or changed form has a submit test covering success path AND at least one validation failure path
  • Every hash-anchor deep link (e.g. #website-logo) has a test asserting both that the right tab becomes active AND the target element is in the viewport after scroll
  • Every route that uses window.location.assign or window.location.hash has a regression test for hash-change handling (AdminDashboard must listen for hashchange, not only read hash on mount)
  • Every wizard/multi-step flow has a spec that walks the entire sequence and asserts progress indicator updates, state persists, celebration fires
  • Every email-link landing page has a spec that opens it (token-gated or not) and asserts expected content — no 404, no redirect to /?auth=invalid
  • Mobile viewport (iPhone 12, 375×812) — all tap targets measured ≥ 44×44px via locator.boundingBox(). No text-link buttons with no padding
  • Tested against production or a preview deploy — NOT localhost alone. WebFetch results do not satisfy this check
  • Opened the real page in a real browser at least once before claiming the feature works

Canonical failure mode (2026-04-14 wizard bug): window.location.assign(samepath#hash) is a soft hash change. Browsers do NOT re-mount the tree — they fire a hashchange event. If the consuming component only reads window.location.hash on initial mount, the tab never switches and nothing visible happens. Always pair programmatic hash navigation with a hashchange listener and a click-through test.

Quick check:

# In the changed repo
pnpm exec playwright test e2e/*-clickthrough.spec.ts --project=desktop --project=iphone
# Then manually open the deployed URL on your phone and tap each button

See also: feedback_click_through_tests_mandatory.md (memory), feedback_playwright_production_testing.md (memory).


8. Multi-Property Branding (if you touched shared components or sub-brand pages)

  • LayoutShell excludes sub-brand paths/social, /sermons are bare (no CWA header/footer)
  • No regex in 'use client' components — use string methods (formatters strip backslash escapes)
  • Sub-brand auth pages show correct branding — not ChurchWiseAI defaults
  • Stripe redirect URLs detect hostname — sermonwise.ai users stay on sermonwise.ai

9. Build Verification

  • pnpm build passes — zero TypeScript errors, zero compilation failures
  • No new warnings introduced — check build output
  • Commit message is clear — describes what AND why

10. Product Knowledge (if you changed features, pricing, or flows)

  • Updated product_knowledge table — chatbot and voice agent query this at runtime
  • Updated PRICING.md — if any price or Stripe ID changed
  • Updated CLAUDE.md — if architecture or ownership changed

11. Payment Flow Integrity

Every checkout flow must follow the payment-first principle: no database records until Stripe confirms payment.

  • No pre-payment DB writes: Submitting onboard/claim forms creates NO premium_churches, churches, identities, or organization_settings records
  • Webhook creates all records: After checkout.session.completed, verify: premium_churches (status=active), churches row, identities, organization_settings, church_team_members all exist
  • Failed/abandoned checkout = clean DB: Abandon checkout mid-flow, verify no orphan records remain
  • Welcome email post-payment only: No emails sent until webhook confirms payment
  • MailerLite sync post-payment only: Subscriber added only after webhook, respecting marketing_opt_in
  • Return page handles webhook delay: Return page polls for record, shows spinner, handles 30s timeout gracefully
  • Webhook idempotency: Duplicate webhook events don't create duplicate records (webhook_events table prevents this)
  • USD currency enforced: All checkout sessions force currency: 'usd' regardless of customer location
  • Stripe metadata complete: All customer data passed via Stripe session metadata (church_name, contact_name, email, phone, tier, etc.)
  • Upgrade flow preserved: Existing customers can upgrade plans via admin dashboard without breaking

Automated Enforcement (Future)

These checks will be automated via:

  • Claude Code hooks — PreToolUse blocks SELECT *, PostToolUse runs type check
  • Custom ESLint rules — client/server boundary, NEXT_PUBLIC_ audit
  • Playwright SEO suite — canonical URLs, OG tags, sitemap validation per domain
  • Gitleaks pre-commit — secret scanning before commit
  • Health endpoint — runtime Supabase + Stripe connectivity check