Live pair-programming QA for the newsletter platform: customer, admin, newsletter send, advertiser sign-up, and billing. Two testers β the agent (AS Dev) and Mike β execute the same steps with different accounts and data, compare results, and file GitHub issues for every mismatch.
[pair-test FLOW<STEP>] short summary β e.g. [pair-test A5] subscribe toast missing on Henderson.pair-test; file in microspaces/aroundtown (CRM-side findings get crm in the title and are cross-linked).MEDIA: and committed under test-screenshots/pair-test/ on the session branch.| Thing | Value |
|---|---|
| Production sites | aroundsummerlin.com Β· aroundhenderson.com Β· aroundcarsoncity.com Β· aroundpahrump.com Β· aroundtownnewsletter.com (company) |
| CRM (admin) | crm-microspaces-projects.vercel.app β tenant/site switcher in the header; Newsletter and Advertisers pages |
| Local dev (optional) | cd ~/Repos/aroundtown && pnpm install && pnpm --filter aroundtown-web build && pnpm --filter aroundtown-web dev β http://localhost:8080. Town resolved from Host header β for API checks use curl -H "Host: aroundsummerlin.com" http://localhost:8080/api/issues |
| Sender pipeline | sender-newsletter.ps1 (aroundsummerlindev workspace). Status: dry-run verified, live send pending token β Flow C starts by confirming this. |
| Stripe billing | Keys (STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET) not yet set β Stripe steps are expected to fail; the failure itself is a test case. |
pairtest.<step>.agent@example.com (CRM record is the oracle, no inbox needed). Mike uses his own inbox with plus-tags: you+pairtestA5@β¦ so records are filterable and he can receive test mail.pairtest-YYYYMMDD-<town> (e.g. pairtest-20260927-summerlin).PairTest <step> <who> β¦ company names (e.g. PairTest D1 Agent Coffee Co) β easy to bulk-find and clean up.Persona: a Summerlin resident discovering the newsletter. Two towns exercised to prove per-town data isolation.
https://aroundsummerlin.com fresh (hard reload), desktop.
{{TOKEN}} text, no console errors.issue.html?slug=β¦.
MikeSame, plus type a search term that matches one issue and one that matches nothing.
published issues (never drafts); search filters live; no-match shows the empty message./#issues.issue.html?slug=definitely-not-real.
pairtest.a5.agent@example.com. Watch the button state during the request.
MikeSubscribe with you+pairtestA5@β¦; then try an empty submit and notanemail.
https://aroundhenderson.com (each with your own A7 email).
Persona: the editor creating and publishing the weekly issue. Feed data: none. Produces: the draft/published issue used by A4 and Flow C.
https://crm-microspaces-projects.vercel.app. If a login wall appears, stop and note it. Use the header switcher to select the Summerlin tenant/site.
[pair-test B1] CRM has no authentication).pairtest.a5.agent@example.com, "Newsletter Subscriber").
MikeContacts page: find your A5 + A7 records and check their site assignment.
pairtest-<date>-summerlin, short summary, 2β3 paragraph body, volume/issue numbers, status draft.
MikeCreate your own with slug pairtest-<date>-summerlin-m and a different body.
draft badge./api/issues, the homepage archive, and issue.html?slug=β¦ for your slug.
published). Then check the public site.
/api/issues now includes it with only public fields (no internal CRM fields leaked in the JSON).Persona: the operator mailing the weekly issue. Feed data: Mike's address in the sender.net test group; the B6 published issue. Never sends to live subscriber groups.
sender-newsletter.ps1 and check for a sender.net API token (.env.sender / masked entry). Confirm the sending domain's SPF/DKIM in the sender.net dashboard.
sync with -DryRun: confirm CRM subscribers β sender.net group mapping, test contacts excluded.
MikeReview the dry-run list; confirm none of the pairtest.*@example.com addresses are in it and his address is.build the email HTML for the B6 issue; inspect the file before any send.
send -DryRun targeting the test group, then (token permitting) the live test-group send.
MikeConfirm receipt in his inbox; check rendering in Gmail web + phone mail app.
sync and Mike checks his CRM contact/subscriber state.
Persona: a local business owner filling the Advertise section. Feed data: none. Produces: the lead that Flow E bills.
pairtest.d1.agent@example.com, package Issue sponsor, no phone/website.
MikeSame with your company data including phone + website + a notes line.lead; empty phone/website stored as null (agent's) vs populated (Mike's); correct tenant/site.curl -X POST /api/advertise with missing fields and with a bad email to test the server side directly.
400 with "Company name, contact name, and a valid email are required." Server must enforce independently of the browser.active with package "Founding Local β sidebar + mid-issue mention, $299/mo".
Persona: the operator billing the D4 advertiser. Feed data: the active advertiser from D4.
amountCents 29900, description "Founding Local β first month", method manual, status pending.
MikeCreate yours with method stripe instead.paid.
checkout.session.completed webhook auto-marks the payment paid.Fill in during the session. "Match" = agent and Mike saw the same behavior. Every β /β row needs an issue link.
| Step | Agent | Mike | Match | Issue | Notes |
|---|---|---|---|---|---|
| A1 | |||||
| A2 | |||||
| A3 | |||||
| A4 | needs B3/B4 first | ||||
| A5 | |||||
| A6 | behavior undocumented | ||||
| A7 | isolation check | ||||
| A8 | |||||
| B1 | auth? security finding | ||||
| B2 | |||||
| B3 | |||||
| B4 | |||||
| B5 | |||||
| B6 | |||||
| B7 | Mike archives after C3 | ||||
| C0 | token gate β likely blocked | ||||
| C1 | |||||
| C2 | |||||
| C3 | |||||
| C4 | behavior undocumented | ||||
| D1 | |||||
| D2 | |||||
| D3 | server-side validation | ||||
| D4 | |||||
| E1 | |||||
| E2 | |||||
| E3 | Stripe keys unset β expected fail | ||||
| E4 | 0/negative amounts |