Local QA
Use this page as the main local verification runbook for the starter.
Fast code checks
Use these for normal implementation work:
bun run lint
bun run format:check
bun run test:unit
bun run test:contracts
bun run test:integration
bun run buildFor the full repo gate:
bun run checkShared Clerk QA accounts
Use two real users:
- no-access user for sign-in,
/start, wrong-account, and pre-purchase checks - granted-access user for
/start,/product,/design,/marketing,/build-plan,/review,/settings, and the optional/execute,/mission, and/lesson/*bonus-route checks after review unlock
Commands:
bun run clerk:test-user:check
bun run clerk:test-user:create
bun run clerk:test-user:check:entitled
bun run clerk:test-user:create:entitled
bun run clerk:test-user:prepare:entitledbun run clerk:test-user:prepare:entitled is the fastest path when your hosted Convex dev deployment is already synced into apps/web/.env.local, because it creates the entitled Clerk QA user if needed and grants product access on the active hosted dev deployment.
Use bun run qa:personas:prepare -- --pack=<pack> when you need the canonical QA control-plane path instead of the one-user local shortcut. The fast helper is for a single entitled debug account. The QA command is the durable multi-persona setup path used by the packs.
The Playwright sandbox bootstrap now normalizes product access as part of auth-state creation. The non-entitled persona is reset back to no_access, and the entitled persona is granted access again before storage state is written. If the no-access browser lane ever starts behaving like an entitled user, treat that as Convex deployment drift or bootstrap failure first, not as a UI bug.
The canonical QA credentials live in ignored qa/personas.local.json. The root qa:* commands and the Playwright sandbox bootstrap both read that file for the no-access and entitled account emails and passwords.
Full paid local handoff
Use this only in a safe local test environment.
- Start the app against your hosted Convex dev deployment.
- Start the Stripe listener.
- If the listener prints a fresh webhook secret, restart the web app.
- Sign in with the no-access account.
- Start checkout from the landing page.
- Complete payment with Stripe test card
4242 4242 4242 4242, any future expiry, any CVC, ZIP10001. - Confirm Stripe returns to
/checkout/success?session_id=.... - Confirm the webhook grants access.
- Confirm the app redirects into
/start.
On a new machine or after switching deployments, run bun run dev:convex first so apps/web/.env.local contains the current NEXT_PUBLIC_CONVEX_URL, NEXT_PUBLIC_CONVEX_SITE_URL, and CONVEX_DEPLOYMENT values. In this repo, the Stripe listener must forward to {NEXT_PUBLIC_CONVEX_SITE_URL}/stripe/webhook, not the .convex.cloud client origin.
Before you trust a local billing run, check the pinned Stripe CLI guardrail once:
bun run stripe:dev:doctorThat command compares the app Stripe account from apps/web/.env.local against the repo-owned Stripe CLI project ship-by-sunday. If it tells you the profile is missing or mismatched, repair it with stripe login --project-name ship-by-sunday. The billing flow can still be env-correct while the CLI profile is noisy, but the repo now treats that as a warning you should resolve before blaming auth or webhooks.
The easiest way to run the full buyer-flow stack after that is:
bun run dev:fullThe current scripted proof for the paid handoff is:
QA_ALLOW_BILLING_INTEGRATION=1 PLAYWRIGHT_USE_LOCAL_SERVER=0 PLAYWRIGHT_BASE_URL=http://localhost:3000 bun run qa:run-pack -- --pack=billing_integrationFor local targets, the billing pack now runs bun run stripe:dev:doctor as a warning-only preflight and bun run stripe:dev:verify as the hard post-checkout proof after the sandbox billing journey finishes.
Hosted and production QA
For any non-local target, keep these boundaries straight:
qa/personas.local.jsonholds the QA email and password pairs.apps/web/.env.localorQA_ENV_FILE=<path>selects the Clerk and Convex environment where those personas will be created or reused.PLAYWRIGHT_BASE_URL=<target>selects the site the browser tests will hit.
Use the root QA control-plane commands for hosted runs instead of stitching Playwright calls together by hand.
Hosted production example:
PLAYWRIGHT_BASE_URL=https://www.man-meets-ai.com QA_ENV_FILE=apps/web/.env.production bun run qa:run-pack -- --pack=default_shipIf the loaded Clerk env does not match the hosted target, persona prep now fails immediately with a Clerk publishable-key mismatch. That is intentional. Fix the env selection before treating any authenticated browser failure as an app bug.
Recommended flow checks
For the deterministic browser lane that runs in PR CI:
set -a
source apps/web/.env.local
set +a
bun run test:browser-smokeThis requires NEXT_PUBLIC_CONVEX_URL from your hosted Convex dev deployment to be present in the shell environment. Run bun run dev:convex first if apps/web/.env.local is stale, then source apps/web/.env.local before running the smoke lane.
This smoke lane now proves the auth-route shell too: /sign-in, /session-tasks/reset-password, and /sso-callback must all render and emit noindex, not just the older /sign-in meta-tag check.
Signed-out protected-route smoke
- Visit
/startor/settingswhile signed out. - Confirm redirect into
/sign-inwith the current protected path preserved inredirect_url.
Identity route smoke
- Open
/sign-inwith no query params and confirm the branded split shell renders with the quietSee pricingpath. - Open
/sign-in?redirect_url=%2Fsettingsand confirm the same shell renders but the pricing path is hidden because the route is in recovery mode. - Open
/sign-in?auth_error=purchase_requiredand confirm the Google purchase-required banner appears without reintroducing the pricing CTA. - Open
/session-tasks/reset-passwordand confirm the route keeps the same branded shell while leaving the Clerk reset form itself intact. - Open
/sso-callbackand confirm the loading route uses the same shell language instead of a bare spinner page.
No-access flow
- Sign in with the no-access account.
- Visit
/start. - Confirm the page explains that the account has no active access and shows which email is currently signed in before you treat it as a Stripe or Convex failure.
Granted-access flow
- Run
bun run clerk:test-user:prepare:entitled. - Sign in with the granted-access account.
- Confirm
/start,/product,/design,/marketing,/build-plan,/review, and/settingsload. - Before unlocking Execute / Ship, confirm direct
/missionand/lesson/<route-segment>visits show explicit locked-bonus messaging instead of forwarding into/start. - On
/review, confirm the page shows the non-clickableWhat’s inside the Starter Snapshotpreview, clickDownload Starter Snapshot, trigger the separateUnlock Execute / Shipaction, and confirm/executeloads as the bridge page. - From
/execute, confirm the primary CTA opens lesson3.1and the secondary CTA opens lesson1.1. - Return to
/executeafter a bonus lesson has been opened and confirm repeat visits now redirect into/mission. - Confirm
/missionloads as the bonus library with the top block rail in naturalProduct -> Design -> Build -> Shiporder, defaulting the active block to Build. - Confirm direct
/lesson/<route-segment>visits render the restored video-first bonus lesson path with the top block rail plus the bottom lesson skill rail, and without workbench inputs.
Observability smoke
Use this when you need real incident packets instead of unit-test confidence.
- Confirm
apps/web/.env.localhasNEXT_PUBLIC_CONVEX_URL,NEXT_PUBLIC_CONVEX_SITE_URL,OBSERVABILITY_INGEST_TOKEN,TELEGRAM_BOT_TOKEN, andTELEGRAM_CHAT_ID. - Run the narrow observability validation first:
bunx vitest run tests/unit/observability-*.test.ts tests/unit/clerk-webhook-adapter.test.ts tests/unit/vercel-webhook-adapter.test.ts tests/unit/stripe-observability.test.ts tests/unit/stripe-webhook-action.test.ts tests/integration/observability-client-error-route.test.ts tests/integration/observability-clerk-route.test.ts tests/integration/observability-vercel-route.test.ts tests/integration/observability-incident-status-route.test.ts tests/integration/stripe-checkout-route.test.ts- Use signed Clerk and Vercel smoke requests plus one client-error request.
- Inspect the target Convex deployment with
bunx convex data incidents --format jsonLines. - Use the public incident status route to set a live incident to
acked, replay the same event, and confirmoccurrenceCountincreases whiledeliveryCountstays fixed.
The currently proven live path is Clerk + Vercel + client-error ingress plus live incident status control. The only remaining unproven live path is the authenticated Codex runtime route.
What success looks like
You are done when the app passes all four realities:
- signed out
- signed in without access
- signed in with granted access
- newly paid and waiting for access grant
Anything less is partial confidence, not real QA.