ChurchWiseAI Pro Website — Expected Output Spec
Promoted from DRAFT to COMPLETE on 2026-04-22 via founder interview (Session 3 of the /ensure-solid portfolio remediation). The 7 delta sections below capture the founder's canonical answers on CWA-specific behavior vs the legacy PewSearch path.
Scope of this spec (vs legacy PewSearch path)
This spec describes the standalone ChurchWiseAI Pro Website: a pastor who never heard of PewSearch sees a ChurchWiseAI ad or email, lands on churchwiseai.com/pro-website, pays $14.95/mo (site-only) or $19.95/mo (bundled with Chat Starter), and ends up with a site at <slug>.john316.church.
The legacy path — claim a listing on PewSearch and upgrade from Premium to Pro Website at pewsearch.com — is fully specified in pewsearch-pro-website.md. That spec covers the existing 3 customers (st-joseph-parish, the-bridge-church, zionlutheran).
What inherits from pewsearch-pro-website.md (no CWA delta expected)
All of these touchpoints have identical expected outputs. Read pewsearch-pro-website.md:
- Admin dashboard shared tabs (Details, Care, Settings)
- Website editor (hero photo/video, staff, events, service times, beliefs, contact form)
- Subdomain SSR rendering (just
.john316.churchinstead of.pewsearch.com) - Stripe checkout + provisioning (same webhook, same
premium_churchesrow shape) - Cancellation flow
- Payment failure handling
- Magic-link admin access
CWA-specific deltas (the 7 founder-interview answers)
1. Discovery copy — pain-led hero
Voice: Pain-led, direct, conversational. Not pastoral sermon-voice. The pastor is a stranger — they have 8 seconds to decide whether to keep reading.
Canonical hero copy (H1 or sub-hero on churchwiseai.com/pro-website):
"You've been meaning to update your church's website for two years. Let's do it tonight, for $14.95/month."
Expected output when checking the landing page:
- Hero contains the full phrase above (or a near-literal variant that preserves "You've been meaning" + "Let's do it tonight" + the
$14.95/monthprice anchor) - Sub-hero reinforces practical ministry tools — no sermon-voice prose here
- First CTA within the hero viewport: "Get started" or "Start tonight" (button text, not sermon language)
Anti-pattern (fail the check):
- Abstract "Missional technology for modern ministry" messaging
- Scripture quote in the hero
- References to PewSearch (this is a standalone funnel)
- Leading with anything other than the pain of "website isn't done"
2. Brand voice — split (marketing vs in-product)
Rule: Marketing copy is pain-led/direct. In-product messaging (errors, empty states, confirmations, HELP text) is Full HEAR voice — warm, sacred, pastoral.
Marketing copy examples (PASS):
- Hero, pricing table, landing CTAs: direct — "Let's do it tonight", "Start for $14.95/mo", "No contracts, cancel anytime"
- Checkout page: transactional + minimal
In-product messaging examples (PASS — Full HEAR voice):
- Form error: "Something isn't quite right — let's pause and look at it together."
- Empty state on Staff section: "No one listed yet. Your team matters — add them when you're ready."
- Save confirmation: "Your changes are live. Your visitors are already seeing this."
- Contact form reply auto-response: "We've received your message. You'll hear back from a real person soon."
Anti-pattern (FAIL):
- Marketing hero uses HEAR-voice prose ("Missional technology for the growing church...")
- Form error reads like a generic SaaS app ("Invalid input. Please try again.")
- Mixing both voices in the same surface — pick one per page based on role
3. Onboarding wizard — structured-before-payment
Flow order for churchwiseai.com/onboard:
- Church name (text input, required) — establishes identity
- Denomination (dropdown, required) — drives template auto-selection (liturgical / community / protestant per
denomination-labels.ts) - Service times (required, at least one) — most critical content for visitors; having it primed before launch makes the site immediately useful
- Key staff name (optional but strongly prompted — "Add a senior pastor or primary contact") — humanizes the site
- Email (required) — admin contact + magic link destination
- Stripe checkout (embedded, $14.95 site-only default; toggle to $19.95 bundled)
- Redirect → magic link email → admin dashboard, site already live at
<slug>.john316.church
Rationale (captured for future agents): "Real content first; site usable when payment clears." The pastor types meaningful data before they're asked to pay, which commits them. By the time payment clears, they see a site with their actual service times + staff — not placeholder content. This is a deliberate contrast to the "blank-slate, edit-later" pattern.
Expected outputs when testing the wizard:
- Step 1: Church name field accepts any non-empty string
- Step 2: Denomination dropdown includes all 17 theological lenses + "Non-denominational / Other"
- Step 3: Service times field requires at least 1 entry (day + time); users can add multiple
- Step 4: Staff name field is optional (skippable) but the UI nudges toward entering one
- Step 5: Email accepts any valid RFC-5321 format; triggers MailerLite newsletter subscription (see section 5)
- Step 6: Stripe checkout shows the $14.95 price by default, with a checkbox or toggle to add Chat Starter → $19.95 bundled. Stripe session metadata carries
tier: cwa_pro_websiteorcwa_pro_website_bundled. - Step 7: Redirect URL is
churchwiseai.com/admin/[admin_token]?onboarded=1
Anti-pattern (FAIL):
- Wizard asks for Stripe payment before collecting content ("fast-to-magic" pattern rejected)
- Wizard requires 10+ fields before payment (scope creep)
- Wizard doesn't show service times field (most common failure mode — pastor provisions, then never adds service times, site is useless)
4. Upsell UX — contextual only, triggered by intent
Rule: Voice Agent and Chatbot upsells appear ONLY when the pastor is interacting with a feature that reveals the need. No blanket banners, no dedicated "Add AI Tools" tab, no interruption on login.
Trigger matrix:
| Trigger action in admin | Upsell surface |
|---|---|
| Pastor adds or edits a phone number on the Contact page | Inline card: "Have this number answered 24/7 for $49.95/mo. Voice Agent learns your church's info and handles calls when no one's available → Add Voice Agent" |
| Pastor edits a visitor-focused page (what-to-expect, service times) | Inline card: "Give visitors live answers on your website. Chatbot handles typical questions for $14.95/mo → Add Chatbot" |
| Pastor saves contact form 10+ submissions in a week | Overview tab card: "You're getting real volume — the Chatbot can handle 80% of these automatically" |
| Pastor adds a crisis-care-adjacent item (funerals, prayer requests on Care tab) | Inline card for Voice Agent with HEAR-specific framing |
Expected outputs:
- No "Add AI Tools" top-level nav entry
- No modal pop-up on login
- No sidebar ad / footer banner
- Upsell cards dismiss-on-click and don't reappear for 30 days unless a new trigger fires
- Copy in each card uses HEAR voice (system messaging, per section 2)
Anti-pattern (FAIL):
- Static ad on Overview tab that never dismisses
- Modal that interrupts the admin flow
- Upsell that appears before the pastor has done any meaningful work
5. SermonWise cross-sell — sermons section only
Rule: SermonWise ($19.95/mo) is surfaced ONLY on the Sermons section of the Content editor, and ONLY when the pastor is actively uploading or editing a past sermon.
Expected output:
- Sermons section shows a compact card below the "Add new sermon" form: "Preparing Sunday's sermon? Try SermonWise — AI outlines aligned to your tradition. $19.95/mo → Learn more"
- Card links to sermonwise.ai (new tab) with UTM tagging:
?utm_source=cwa_admin&utm_medium=cross_sell&utm_campaign=pro_website_sermons - Card persists on the Sermons section but does not appear on other tabs
Anti-pattern (FAIL):
- Cross-sell card in the Overview tab
- Cross-sell email blast to all Pro Website customers (handle via MailerLite automation if needed, not the admin UI)
- Cross-sell that doesn't include UTM tagging (can't measure conversion)
6. Welcome email sequence — 3 emails
All send via MailerLite, triggered by the cwa_pro_website or cwa_pro_website_bundled subscription-created webhook event.
| Day | Subject | Body summary | From |
|---|---|---|---|
| 0 (minutes after payment) | "Your website is live at <slug>.john316.church" | Magic link + 3-sentence quickstart (hero photo, service times, staff) + reply-to | john@churchwiseai.com |
| 3 | "5 things most pastors miss when setting up their site" | Feature tour email covering: hero photo tips, service times precision, staff bios with photos, a welcome-to-visitors blurb, Google-ability (SEO basics) | john@churchwiseai.com |
| 7 | "How's it going?" | Founder check-in, reply-to: "I read every one. What's working? What's frustrating? — John" | john@churchwiseai.com |
Expected output:
- Day 0 email arrives within 5 minutes of Stripe
checkout.session.completedwebhook - Day 3 email arrives at [signup_time + 3 days] ± 2 hours
- Day 7 email arrives at [signup_time + 7 days] ± 2 hours
- All three emails reply-to
john@churchwiseai.com(not a do-not-reply) - MailerLite group:
cwa-customers(canonical — verified inknowledge/readiness/cwa.yamlMailerLite reconciliation 2026-04-22) - Pastor can unsubscribe from Day 3 / Day 7 without losing the magic-link relationship (separate tags)
Anti-pattern (FAIL):
- Day 0 email missing or >15min after payment
- Emails from
noreply@ordo-not-reply@ - No reply-to support address
- Emails skip the Day 3 / Day 7 follow-up (reduces founder signal on real-customer usage)
7. Domain config — default *.john316.church, custom domain on bundled tier
Rule:
- Default subdomain:
<vanity_slug>.john316.church— auto-provisioned at onboarding for all tiers - Custom domain: available only on the $19.95 bundled tier (not site-only $14.95). Pastor enters their own domain (e.g.,
graceville-baptist.org) in Settings. ChurchWiseAI provisions via Vercel domain API, handles CNAME instructions + auto-provisions SSL. - Custom domain setup fee: $49.95 one-time, waived on annual bundled subscriptions. Setup fee applies whenever custom domain is first added to the account — including existing annual customers who later want to add a custom domain (annual discount covers the base product, not future add-ons).
Subdomain provisioning (all tiers):
- At onboarding,
<vanity_slug>is derived from church name (slugified, collision-resolved with-Nsuffix) - Subdomain routing handled by Next.js middleware at
churchwiseai-web/src/middleware.ts—{slug}.john316.churchrewrites to/s/{slug}internally - DNS wildcard
*.john316.churchpoints at Vercel's IP — no per-customer DNS work needed for subdomain path - SSL auto-provisioned via Vercel wildcard cert
Custom domain provisioning (bundled tier only):
- Pastor adds desired domain in Admin → Settings → Domain
- UI shows CNAME instructions: "Add a CNAME record at
wwwpointing tocname.vercel-dns.com" - Once DNS propagates, ChurchWiseAI's cron (or webhook-triggered) verifies CNAME and triggers Vercel domain add via API
- Vercel auto-provisions SSL (Let's Encrypt), typically 5-15 min
- Admin UI shows status:
Pending DNS → Verifying → SSL provisioning → Live - Email confirmation to pastor when live
- Fallback: support email
hello@churchwiseai.comfor DNS edge cases
Stripe integration for the $49.95 setup fee:
- Setup fee is a ONE-TIME invoice item added to the customer's next monthly invoice (not a separate checkout session)
- Line item description: "Custom domain setup — one-time"
- On annual bundled subscriptions: setup fee is created with a 100% coupon applied at invoice generation → shows on invoice as $0 (so the pastor sees the waiver, not a hidden silent skip)
Expected outputs when testing:
- Site-only tier ($14.95) admin does NOT show "Add custom domain" input — shows a locked/upgrade tombstone card only
- Bundled tier ($19.95) admin shows "Domain" section with: current subdomain (read-only), "Add custom domain" input form
- Adding a custom domain on monthly bundled tier creates a $49.95 invoice item on the next bill (Stripe
invoiceItems.create) - Adding a custom domain on annual bundled tier creates a $49.95 invoice item with 100% coupon → $0 effective (coupon shows on invoice — not silently skipped)
- After domain is added, UI transitions to
pending_dnsstate showing CNAME instructions (www → cname.vercel-dns.com) - "Verify DNS" button is a MANUAL REFRESH — it calls
GET /api/premium/domain/verifyon click; there is no auto-polling in the current implementation (v1). If auto-polling is added later, it should poll every 30 seconds with backoff, stop after 'live' or 'error' status, and show a countdown timer. - Status lifecycle:
none → pending_dns → verifying → ssl_provisioning → live(orerrorat any step) - If
VERCEL_API_TOKENis not configured, the UI shows "Vercel integration not configured — contact support at hello@churchwiseai.com" rather than a raw error - Custom domain removal disables the custom domain but does NOT refund the setup fee
Implementation notes (for future agents):
DomainSection.tsxself-gates — renders nothing for non-Pro-Website plans, tombstone for site-only, full workflow for bundledcanAccess(plan, 'custom_domain')returnstrueONLY forcwa_pro_website(notcwa_pro_website_site_only,ps_pro_website, or legacy keys)stripe-invoice-items.ts → addCustomDomainSetupFee()handles the coupon logic for annual subscriptionsvercel-domains.ts → addDomainToProject()/checkDomainStatus()wrap the Vercel REST API — requireVERCEL_API_TOKENenv var- DB columns added:
custom_domain_status,custom_domain_added_at,custom_domain_setup_fee_paid_at - Auto-polling v2 change surface: replace the manual "Verify" button in
DomainSection.tsxwith auseEffectinterval; add asetInterval(handleVerify, 30_000)that clears onliveorerrorstatus
Anti-pattern (FAIL):
- Site-only tier admin shows "Add custom domain" (misleading — feature is tier-gated)
- Setup fee charged as a separate Stripe checkout session (should be an invoice item on the existing subscription)
- Custom domain silently fails DNS verification without user-facing status
- No support fallback path when DNS is misconfigured
Public site — Service Times + Office Hours tabbed widget + About (2026-04-24 delta)
Replaces the former day-card 3-col grid. Server-rendered, zero JS, no client components — the prior HoursTabs 'use client' attempt triggered React hydration error #418 (2026-04-23). Revised 2026-04-24 evening from two stacked sections to a single tabbed widget per founder direction.
Service Times + Office Hours — CSS-only tabbed widget
One section on the page showing one pane at a time. Service Times is the default visible pane. A tab click switches to Office Hours. Pure CSS state — two hidden radio inputs + sibling :checked ~ selectors drive visibility. No React state, no hydration.
Markup contract (when both service times AND office hours are configured):
- One
<section id="hours"> - Heading: "When We Gather" eyebrow + "Visit Us"
<input type="radio" name="cwa-hours-view" id="cwa-htab-services" defaultChecked>+<input ... id="cwa-htab-office">inside adiv.cwa-hours-tabsscope<div role="tablist">wraps two<label role="tab">elements (one per pane)<div role="tabpanel" class="cwa-pane cwa-pane-services">and<div role="tabpanel" class="cwa-pane cwa-pane-office">- Scoped
<style>block hides.cwa-paneby default, reveals the one matching the:checkedradio sibling, and styles the active<label>white-with-shadow
Content contract:
- Both panes render
<dl>with day → time rows (bold day label,·-joined times) - Office Hours pane has a subtitle: "When our church office is staffed and you can call or drop in."
- Days ordered Sunday → Saturday via
sortHoursDays()
Accessibility:
role="tablist"+role="tab"+role="tabpanel"+aria-labelon tablist +aria-labelledbyon panels- Radio inputs keyboard-focusable; arrow keys switch panes natively (browser radio group semantics)
focus-visibleoutline on active tab label
Fallback: only one of hours/office_hours configured → no tab UI. Single panel renders inline (<section id="times"> or <section id="office-hours">) with the appropriate eyebrow + heading. Neither configured → section omitted.
Why tabs, not stacked: maximizes one-page-site real estate. Typical church has 1–3 service rows + 3–5 office-hour rows; showing both takes ~600–900px vertical. Tabbed widget collapses to one panel (~200–300px) with a one-click toggle.
About section copy — single source of truth
- Edited only in admin Website tab → About your church (draft-aware; Publish required).
- Settings → Basic Information no longer renders a Description field; it shows a one-line link back to Website → About.
/api/premium/updatecase 'basic'is conditional-write oncustom_description(only mutates when the field is present in the submittedFormData). This prevents the Settings form from nulling canonical Description on save.
Code files (new/changed 2026-04-24):
churchwiseai-web/src/components/templates/UnifiedTemplate.tsx— slider markupchurchwiseai-web/src/components/admin/forms/ChurchProfileForm.tsx— Description removed + hint addedchurchwiseai-web/src/app/api/premium/update/route.ts— conditionalcustom_descriptionwrite
Regression guard: e2e/pro-website-slider.spec.ts — renders a vanity-subdomain page and asserts: (a) worship <dl> contains all configured day labels, (b) office-hours <ul> has one <li> per configured day, (c) scroll container has snap-x class, (d) zero pageerror events on load.
User State Definition
| Field | Value |
|---|---|
| Plan | cwa_pro_website (site-only, $14.95/mo) OR cwa_pro_website_bundled (bundled w/ Chat Starter, $19.95/mo) |
| Status | preview → active |
| Admin URL | churchwiseai.com/admin/[admin_token] |
| Auth | Token-based magic link |
| Public URL (default) | [vanity_slug].john316.church |
| Public URL (custom, bundled only) | Customer's own domain after DNS + SSL provisioning |
| Trial | None (preview mode serves this purpose) |
| Price | $14.95/mo (site-only) or $19.95/mo (bundled) |
| Custom domain setup fee | $49.95 one-time (bundled only), waived on annual |
| Chatbot | Included in bundled tier ($19.95); not in site-only ($14.95) |
| Upsells visible in admin | Voice Agent + SermonWise, contextually only (per sections 4 and 5) |
Pricing cross-reference
| Item | Price | Stripe Test ID | Stripe Live ID | Canonical source |
|---|---|---|---|---|
| Pro Website site-only | $14.95/mo | price_1TOkD4FaoK5IPzNoJhTkwveK | price_1TOkeMFaoK5IPzNoDDtpQGor | data/pricing.yaml → cwa_pro_website_site_only |
| Pro Website bundled | $19.95/mo | (see stripe-prices.ts) | (see stripe-prices.ts) | data/pricing.yaml → cwa_pro_website |
| Custom domain setup | $49.95 one-time | price_1TPAksFaoK5IPzNofvyGvjh8 | price_1TPAr4FaoK5IPzNouIdwlcaD | This spec (2026-04-22) + data/pricing.yaml (follow-up) |
| Custom domain add-on fee | $0/mo (included in bundled) | — | — | This spec |
Agents reading this spec
Before modifying any Pro Website code path, cross-reference against this spec. The 7 delta sections above are founder-canonical. If a code change would contradict an expected output here, either:
- Update this spec first (with founder sign-off), then the code, OR
- Flag the contradiction as a FAIL in the /ensure-solid pro-website scorecard
The knowledge/readiness/pro-website.yaml file references this spec under acceptance_specs:. The /ensure-solid pro-website dim-1 and dim-5 checks use this spec's expected outputs as pass/fail criteria.