Skip to main content

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.church instead of .pewsearch.com)
  • Stripe checkout + provisioning (same webhook, same premium_churches row 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/month price 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:

  1. Church name (text input, required) — establishes identity
  2. Denomination (dropdown, required) — drives template auto-selection (liturgical / community / protestant per denomination-labels.ts)
  3. Service times (required, at least one) — most critical content for visitors; having it primed before launch makes the site immediately useful
  4. Key staff name (optional but strongly prompted — "Add a senior pastor or primary contact") — humanizes the site
  5. Email (required) — admin contact + magic link destination
  6. Stripe checkout (embedded, $14.95 site-only default; toggle to $19.95 bundled)
  7. 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_website or cwa_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 adminUpsell surface
Pastor adds or edits a phone number on the Contact pageInline 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 weekOverview 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.

DaySubjectBody summaryFrom
0 (minutes after payment)"Your website is live at <slug>.john316.church"Magic link + 3-sentence quickstart (hero photo, service times, staff) + reply-tojohn@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.completed webhook
  • 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 in knowledge/readiness/cwa.yaml MailerLite 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@ or do-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 -N suffix)
  • Subdomain routing handled by Next.js middleware at churchwiseai-web/src/middleware.ts{slug}.john316.church rewrites to /s/{slug} internally
  • DNS wildcard *.john316.church points 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):

  1. Pastor adds desired domain in Admin → Settings → Domain
  2. UI shows CNAME instructions: "Add a CNAME record at www pointing to cname.vercel-dns.com"
  3. Once DNS propagates, ChurchWiseAI's cron (or webhook-triggered) verifies CNAME and triggers Vercel domain add via API
  4. Vercel auto-provisions SSL (Let's Encrypt), typically 5-15 min
  5. Admin UI shows status: Pending DNS → Verifying → SSL provisioning → Live
  6. Email confirmation to pastor when live
  7. Fallback: support email hello@churchwiseai.com for 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_dns state showing CNAME instructions (www → cname.vercel-dns.com)
  • "Verify DNS" button is a MANUAL REFRESH — it calls GET /api/premium/domain/verify on 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 (or error at any step)
  • If VERCEL_API_TOKEN is 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.tsx self-gates — renders nothing for non-Pro-Website plans, tombstone for site-only, full workflow for bundled
  • canAccess(plan, 'custom_domain') returns true ONLY for cwa_pro_website (not cwa_pro_website_site_only, ps_pro_website, or legacy keys)
  • stripe-invoice-items.ts → addCustomDomainSetupFee() handles the coupon logic for annual subscriptions
  • vercel-domains.ts → addDomainToProject() / checkDomainStatus() wrap the Vercel REST API — require VERCEL_API_TOKEN env 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.tsx with a useEffect interval; add a setInterval(handleVerify, 30_000) that clears on live or error status

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 a div.cwa-hours-tabs scope
  • <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-pane by default, reveals the one matching the :checked radio 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-label on tablist + aria-labelledby on panels
  • Radio inputs keyboard-focusable; arrow keys switch panes natively (browser radio group semantics)
  • focus-visible outline 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/update case 'basic' is conditional-write on custom_description (only mutates when the field is present in the submitted FormData). 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 markup
  • churchwiseai-web/src/components/admin/forms/ChurchProfileForm.tsx — Description removed + hint added
  • churchwiseai-web/src/app/api/premium/update/route.ts — conditional custom_description write

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

FieldValue
Plancwa_pro_website (site-only, $14.95/mo) OR cwa_pro_website_bundled (bundled w/ Chat Starter, $19.95/mo)
Statuspreviewactive
Admin URLchurchwiseai.com/admin/[admin_token]
AuthToken-based magic link
Public URL (default)[vanity_slug].john316.church
Public URL (custom, bundled only)Customer's own domain after DNS + SSL provisioning
TrialNone (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
ChatbotIncluded in bundled tier ($19.95); not in site-only ($14.95)
Upsells visible in adminVoice Agent + SermonWise, contextually only (per sections 4 and 5)

Pricing cross-reference

ItemPriceStripe Test IDStripe Live IDCanonical source
Pro Website site-only$14.95/moprice_1TOkD4FaoK5IPzNoJhTkwveKprice_1TOkeMFaoK5IPzNoDDtpQGordata/pricing.yamlcwa_pro_website_site_only
Pro Website bundled$19.95/mo(see stripe-prices.ts)(see stripe-prices.ts)data/pricing.yamlcwa_pro_website
Custom domain setup$49.95 one-timeprice_1TPAksFaoK5IPzNofvyGvjh8price_1TPAr4FaoK5IPzNouIdwlcaDThis 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:

  1. Update this spec first (with founder sign-off), then the code, OR
  2. 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.