Compare commits

..
2 Commits
Author SHA1 Message Date
joshuaandClaude Opus 5.5 47725a689d Roadmap v3: decisions, pricing and authenticator-app 2FA; handover updated
One install of each product per customer; House of Prayer booking
defaults; email and WhatsApp only (no push); Accounts hidden from
members. Pricing: Website R99 with 5 GB plus storage add-on, App from
R99, address once per church (own domain, crosscode.co.za address R10,
domain included R25). Authenticator app 2FA as Cross Events 2.2.0.
Handover now points at ROADMAP-v3; nothing left open in ROADMAP-v2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:48:23 +02:00
joshuaandClaude Opus 5.5 a9584cdde5 Roadmap v3: Accounts, Church Website, Church App, Cross Code Hub
Plan for selling three products from one Hub: shared users through a
per-church Accounts service, event sync in any direction on one shared
format (crosscode.event/1), PM2 installs per product per church.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:14:24 +02:00
3 changed files with 518 additions and 19 deletions
+21 -17
View File
@@ -1,8 +1,9 @@
# Handover — where things stand and how to carry on
Last updated: 2026-11-03, after **2.1.1** and Hub **1.7.0**. The plan itself is in
[ROADMAP-v2.md](./ROADMAP-v2.md) (decisions table at the end); the previous round of
features is in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
Last updated: 2026-11-03, after **2.1.1** (deployed) and Hub **1.7.0**. **The plan from here is
[ROADMAP-v3.md](./ROADMAP-v3.md)** (Accounts, Church Website, Church App, Cross Code Hub; decisions
table near the end). How Cross Events got to 2.x is in [ROADMAP-v2.md](./ROADMAP-v2.md); the round
before that in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
The product is called **Cross Events** (renamed from Hope Events in 1.41.3); the Hub is the
**Cross Events Hub**. Technical names keep `hope-events` on purpose — the repo, browser storage
@@ -88,24 +89,27 @@ encrypted settings unreadable or break existing installs.
## Next up
1. **After 2.0.x** (order decided 2026-11-02, roadmap §18: 2.0.1 → the marketing site → 2.1.0):
- **Deploy:** `bash scripts/deploy.sh --check`, then `bash scripts/deploy.sh v2.0.1` on each server
(installs on 1.52.x ask through Request update as usual). Before updating an install that has
its own integration, look in the nginx access log for `/api/` calls that aren't `/api/v2/`,
the webhooks or the old links — those get 410 after the update (MIGRATING-TO-2.md).
1. **Roadmap v3** ([ROADMAP-v3.md](./ROADMAP-v3.md), decided 2026-11-03). Everything in
ROADMAP-v2 is done: 2.1.1 is deployed, and the marketing site (`../crosscode-site`,
crosscode.co.za, with prices) is live. Build order (§9):
- **0 — contracts 1.0.0:** `crosscode.event/1`, `crosscode.church/1`, `crosscode.identity/1`,
Hub protocol v3, in the new `joshua/crosscode-platform` monorepo (`packages/contracts`).
- **1 — Hub 2.0.0** (`../events-hub`): Cross Code Hub — products, one licence per product,
**one install of each product per customer**, domain per customer, bundles, sibling discovery.
- **1b — Cross Events 2.2.0:** **authenticator app 2FA** (TOTP + recovery codes) next to email
and WhatsApp (`TwoFactorMethod` enum in `prisma/schema.prisma`).
- **2 — Cross Events 2.3.0:** protocol v3, Linked products, the event feed (publish and show).
- **3 — Accounts 1.0.0**, **4 — Church Website 1.0.0**, **5 — Cross Events 3.0.0** (sign-in
moves to Accounts), **6 — Church App 1.0.0** (House of Prayer).
- **Before selling the Website or App:** put the prices decided in ROADMAP-v3 §10 in
`crosscode-site/src/data/pricing.mjs` and the Hub's `frontend/src/lib/tiers.ts`.
- Servers stay **PM2 + nginx** (no Docker); one database per product per church.
- **AI for a church:** set the licence's monthly AI cap above R0 on the Hub (Hub 1.6.0+); with
no cap the buttons don't show.
- **Marketing site — next, and before 2.1.0** (roadmap §17, all decided 2026-11-02): **crosscode.co.za**, Cross Code's
main site with a Cross Events page; church website and church app as "coming later"; repo
**`joshua/crosscode-site`** (create it on Gitea first); Joshua's own server (nginx); no prices;
a **contact form to info@crosscode.co.za** (needs a small send endpoint on the server — see
§17's building notes); a privacy notice and terms of use; no cookies or analytics. Logo,
colours, wording and screenshots come from Joshua later — start with placeholders.
- **2.1.0** (roadmap §18): the **Feedback page** — results per event, each response, who
hasn't answered and a "remind" action. AI helpers on the privacy pages were dropped
(2026-11-03).
- Testing AI locally: run the Hub (`../events-hub`, port 5200), set Settings → AI there, give a
test licence an AI cap, and start Cross Events with `HUB_URL=http://localhost:5200`.
- **Deploying:** `bash scripts/deploy.sh --check`, then `bash scripts/deploy.sh v<version>` on
each server (installs ask through Request update as usual).
2. **AI rules** (decided 2026-10-29, roadmap §15): the AI buttons show only when the licence's
monthly cap is above R0, and are disabled (not hidden) once it's reached. The Hub charges the
real cost plus a markup % (a default and a per-licence override) and counts the cap in charged
+2 -2
View File
@@ -1200,5 +1200,5 @@ feature.
## Still open
- **Marketing site (§17):** built; waiting on Joshua's real logo, wording and photos where
placeholders remain.
Nothing. The marketing site (§17) is done (2026-11-03). The plan from here is
[ROADMAP-v3.md](./ROADMAP-v3.md).
+495
View File
@@ -0,0 +1,495 @@
# Cross Code — Road to three products
Planned work to go from one product (Cross Events 2.1.x) to three that can be sold separately or
together: **Cross Events**, a **Church Website** and a **Church App**, all managed from one
**Cross Code Hub**. Based on the two concept specs (church website platform, church app v1.1,
3 November 2026), with the changes decided on 2026-11-03 (see [Decisions](#decisions-2026-11-03)).
See [ROADMAP-v2.md](./ROADMAP-v2.md) for how Cross Events got to 2.x.
**Theme:** a church can have any mix of products (Events, Website, App, all, or just the
Website). People sign in to all of them with **one account**, and events flow between them in
whatever direction the church chooses.
---
## Contents
1. [The shape](#1-the-shape)
2. [Contracts (shared JSON formats)](#2-contracts-shared-json-formats)
3. [Accounts (shared users)](#3-accounts-shared-users)
4. [Event sync in any direction](#4-event-sync-in-any-direction)
5. [Cross Code Hub (all products)](#5-cross-code-hub-all-products)
6. [Church Website](#6-church-website)
7. [Church App](#7-church-app)
8. [Servers and deployment](#8-servers-and-deployment)
9. [Build order](#9-build-order)
10. [Pricing](#10-pricing)
11. [Decisions](#decisions-2026-11-03)
12. [Still open](#still-open)
---
## 1. The shape
Each church gets its own **church stack**: one install per product it has bought, each with its
own process and its own database. The Hub only sees aggregate stats and licences.
```
Cross Code Hub (hub.crosscode.co.za)
customers · licences per product · stats · AI proxy · billing
▲ register / heartbeat / licence (install → Hub only)
┌───────────────┼────────────────────────────────────────┐
│ Church stack (e.g. Hope Family Church) │
│ │
│ Accounts (accounts.church.co.za) ◄── sign-in (OIDC) ──┤
│ ▲ ▲ ▲ │
│ Website (www.) ◄─feed─► Events (events.) ◄─feed─► App (app.)
└─────────────────────────────────────────────────────────┘
```
**Rules**
1. **No shared databases, ever.** Products only talk over HTTPS using documented contracts (§2).
2. **The Hub is never on the runtime path.** It stays read-only toward installs: installs push to
it, it never reaches into them, and a Hub outage switches nobody off.
3. **One maintained codebase per product.** Each church runs a tagged release of it. Nothing is
built specially for one church.
4. **The origin of a record owns it.** Copies elsewhere are read-only and link back to the origin.
**Products and what each one owns**
| Product | Owns | Doesn't do |
|---------|------|------------|
| **Accounts** | People's accounts: name, email, phone, password, 2FA, privacy notice acceptance, news consent | Anything product-specific |
| **Cross Events** | Events with registrations, tickets, payments, check-in, kids, rosters, POPIA toolkit | Website content |
| **Church Website** | Public pages, leaders, ministries, messages (sermons), service times, simple events | Registrations or payments |
| **Church App** | House of Prayer, announcements, devotionals, reading plans, groups, care, simple events | Registrations or payments |
---
## 2. Contracts (shared JSON formats)
Everything that crosses between products is defined **once**, in a shared package
`@crosscode/contracts` (zod schemas + generated JSON Schema + TypeScript types + sample payloads).
Every product, including Cross Events and the Hub, uses it. This is what keeps the key fields the
same in every product's JSON.
| Contract | Used for |
|----------|----------|
| `crosscode.event/1` | The event feed every product publishes and reads (§4) |
| `crosscode.church/1` | The church profile: name, logo, colours, campuses, service times, contact details. Set once and shared |
| `crosscode.identity/1` | The claims Accounts puts in a sign-in token, and the back-channel calls (`user.updated`, `user.deleted`, `user.export`) |
| Hub protocol v3 | Register, heartbeat, licence token, AI calls, for every product (§5) |
**Versioning**
- Adding an **optional** field keeps the version (`/1`). Consumers ignore fields they don't know.
- Removing or renaming a field, or changing its meaning, needs a new version (`/2`). The old
version is served alongside it until every install has updated, as with `/api/v2`.
- Every payload carries its `schema` name, so a consumer always knows what it's reading.
- The package lives in the platform monorepo (§8) and is published to Gitea's package registry so
the Events and Hub repos can install it.
---
## 3. Accounts (shared users)
A small per-church **OpenID Connect** provider (`oidc-provider` on the server, `openid-client` in
each product). Every church stack has one, including website-only churches (where it only holds
staff logins).
**Why a separate service:** if one product held the users, cancelling that product would take
everyone's login with it. With Accounts on its own, products can be added or cancelled without
touching anyone's account.
### What lives where
| Accounts | Each product (keyed by the account's `sub`, a UUID) |
|----------|-----------------------------------------------------|
| Name, email, phone, password (Argon2id), 2FA (email, WhatsApp or an authenticator app), lockout, password reset, login history | Roles in that product. An Events supervisor isn't automatically an App prayer coordinator |
| Privacy notice versions and acceptance, news consent (email / WhatsApp) | Product data: registrations and tickets (Events), prayer commitments (App), content edits (Website) |
| "Download my data" and "Delete my account" entry points | Its share of the export and the anonymising, when Accounts asks |
| Inactive-account retention | — |
### How it works
- Sign-in is the standard authorisation-code flow with PKCE. People sign in once at
`accounts.church.co.za` and are signed in to every product of that church.
- **Accounts stays in the background** (decided 2026-11-03). Members never see it as a product:
the sign-in page carries the church's name and branding ("Sign in to Hope Family Church"), and
they manage their account from whichever product they're in. Only **admins** see Accounts as
such (people, sign-in settings, linked products), and **account holders** see their own
account page (profile, password, 2FA, privacy).
- **2FA methods:** email, WhatsApp, or an **authenticator app** (TOTP, e.g. Google or Microsoft
Authenticator), with one-time recovery codes. Cross Events gets the authenticator option first
(2.2.0), and the migration carries it over (below).
- Each product keeps a local copy of the profile (name, email) for lists and messages, updated on
sign-in and by `user.updated`.
- **POPIA:** the church is one responsible party. "Download my data" asks every linked product for
its part (`user.export`) and bundles the answers. "Delete my account" deactivates the account at
once, then sends `user.deleted` to each product when an admin approves (the existing Events
rules apply: it waits while the person has an open registration for an upcoming event).
- Back-channel calls are signed with a secret per product link and retried through pg-boss.
- Accounts gets its own access log entries for exports and admin views of people.
### Moving Cross Events onto Accounts (Cross Events 3.0)
This is a breaking change, so it's a major version.
- A migration copies every Events user into Accounts **with their existing password hash**, 2FA
setting (including authenticator secrets and unused recovery codes), consent and notice acceptance, so nobody has to reset anything. The Events `User`
table keeps its id (now equal to the account `sub`), role, campus scope and product data.
- Sign-in, forgot password, 2FA, the news question and the notice re-acceptance move to Accounts.
The Events privacy centre links to Accounts for account-level items.
- Staff accounts work the same way: roles stay in Events.
---
## 4. Event sync in any direction
Every product can **publish** the events it owns and **show** events from its siblings. Each
install has two settings:
- **Publish my events:** on / off.
- **Show events from:** ☐ Events ☐ Website ☐ App (only linked products are listed).
That covers every setup: Events → Website and App; Events and App → Website; everything to
everything; or nothing.
### Rules
- **Only the origin edits an event.** Imported events are read-only copies with a link back.
Corrections happen at the origin and arrive with the next sync.
- **No loops:** a product only publishes events it *originated*, never ones it imported.
- **No duplicates:** an event is identified by `origin.product` + `origin.id`.
- **Only Cross Events takes registrations and payments.** The Website and App can create
**simple events** (title, dates, place, image, description, and an optional external link or
contact instructions).
- Each product keeps the last good copy of each feed. If a source is down, its events keep
showing, and admins see "last synced 2 hours ago".
### Transport
- `GET /api/feed/v1/events?updatedSince=<ISO>` → `{ schema, generatedAt, events: [...], deleted: [...] }`.
Public events only, so no personal information ever goes through the feed.
- A signed webhook `event.changed` tells consumers to fetch straight away.
- Consumers also poll every 15 minutes as a fallback (pg-boss job).
- Timeouts, retries with back-off, and one failing source never blocks another.
### `crosscode.event/1`
```json
{
"schema": "crosscode.event/1",
"id": "8f1c2a4e-…",
"origin": {
"product": "events",
"installId": "inst_…",
"url": "https://events.hopefamily.co.za/events/youth-camp"
},
"status": "published",
"updatedAt": "2026-11-03T10:00:00Z",
"title": "Youth Camp 2026",
"summary": "Three days at Camp Wartburg",
"description": "Sanitised markdown…",
"imageUrl": "https://events.hopefamily.co.za/uploads/…",
"timezone": "Africa/Johannesburg",
"startsAt": "2026-03-14T08:00:00Z",
"endsAt": "2026-03-16T14:00:00Z",
"allDay": false,
"sessions": [
{ "id": "…", "label": "Day 1", "startsAt": "…", "endsAt": "…", "location": null, "cancelled": false }
],
"location": { "name": "Camp Wartburg", "address": "…", "onlineUrl": null },
"campus": { "slug": "meyerton", "name": "Meyerton" },
"series": { "id": "…", "name": "Youth Camp" },
"tags": ["youth"],
"registration": {
"mode": "origin",
"url": "https://events.hopefamily.co.za/events/youth-camp",
"status": "open",
"opensAt": null,
"closesAt": "2026-03-10T22:00:00Z",
"priceFrom": { "amountCents": 45000, "currency": "ZAR" }
}
}
```
| Field | Values / notes |
|-------|----------------|
| `origin.product` | `events` · `website` · `app` |
| `status` | `published` · `cancelled` (unpublished or deleted events go in `deleted`) |
| `sessions` | Empty for a single-date event. `startsAt` / `endsAt` are always the first start and last end |
| `campus` | `null` = all campuses or campuses off |
| `registration.mode` | `origin` (register at the origin, Events only) · `external` (a link) · `contact` (instructions in the description) · `none` |
| `registration.status` | `open` · `not_open` · `closed` · `full` · `waitlist`. Only Events sends anything but `open` |
| `priceFrom` | Lowest current price in cents, or `null` for free / not applicable |
---
## 5. Cross Code Hub (all products)
The Events Hub (`joshua/events-hub`, Hub 1.7.0) becomes **Cross Code Hub 2.0**: one place for every
product's customers, licences, stats, AI usage and billing.
- **Customer → installs.** Each install has a `product` (`events` · `website` · `app` ·
`accounts`) and its **own licence**. Accounts is licensed with whatever else the church has
(no charge of its own). **A customer has at most one install of each product** (decided
2026-11-03): the Hub refuses a second licence for the same product, so "the church's website"
or "the church's Events" is never ambiguous.
- **Address per customer:** own domain, Cross Code address (`church.crosscode.co.za`) or domain
included (§10), recorded on the customer, since one address covers all its products. The
Hub reminds Joshua before an included domain is due for renewal.
- **Add-ons per customer:** the address (R10 or R25) and extra website storage, billed with the
products but never bundle-discounted. Website installs report storage used in the heartbeat.
- **Billing per customer:** prices per product on the income bands, bundles at **15% off two
products** and **25% off all three**, founding prices and the free month (as on
crosscode.co.za). Invoices are recorded in the Hub (sent / paid / overdue). A payment provider
integration comes later.
- **Protocol v3:** adds `product` to register and heartbeat, and per-product stats (aggregate
counts only, as today: no personal information). The Hub keeps accepting protocol v2, so Events
installs keep reporting until they update.
- **Sibling discovery:** the licence token carries the `customerId`. The heartbeat reply lists the
church's other installs (product, URL, public key; nothing else). Each product shows them
under **Settings → Linked products** with a **Connect** button that sets up the feed (§4) and
the Accounts client (§3). The list is cached, so a Hub outage changes nothing.
- **AI:** the existing proxy, cap and markup serve every product. Usage is recorded per product,
and the cap stays per customer.
- **Request update** works for every product. The Hub still never runs anything on an install.
- **Domain:** `hub.crosscode.co.za`. `events-hub.crosscode.co.za` stays as an alias so existing
installs keep working.
- Dashboard: installs per product, versions behind latest, health, licence status, monthly
recurring revenue, overdue invoices.
---
## 6. Church Website
A professional public website the church manages itself. From the website spec, adjusted.
### Public site
- **Pages:** Home, New Here? / Plan Your Visit, About (story, mission, vision, values, beliefs),
Leadership, Ministries (overview + one page each), Events (list, detail, calendar, past),
Messages (sermons, series), Give (information and provider links), Contact, Privacy /
POPIA / safeguarding / terms.
- **Homepage order:** hero with Plan Your Visit, service times and location, mission,
introduction, ministries and team, upcoming events, latest message, final invitation.
- Branding from `crosscode.church/1`: logo, colours, a choice of themes. Approved section types
that can be reordered, not free-form layout editing.
- Server-rendered for SEO, fast on a phone, accessible (WCAG 2.2 AA, axe checks in Playwright).
### Admin (`www.church.co.za/admin`, signed in through Accounts)
- Dashboard with a setup checklist, recent edits and drafts.
- Pages and sections, About, Leadership, Ministries, Messages, Media library, Contact and
service times, Navigation, Settings.
- **Events:** simple events, plus the feed settings (§4) and sync health.
- **Draft → preview → publish**, archive instead of delete, an audit log of who changed what.
- **Roles:** Owner, Admin, Editor, Ministry leader (own ministry only), Viewer.
### Not in the first release
Prayer requests and other forms with personal information (they belong in the App), a member
directory, online giving processing.
---
## 7. Church App
A church-branded **web app** (PWA) at `app.church.co.za`, added to the home screen. No app-store
release. From the app spec, adjusted.
### House of Prayer (the headline)
- **24/7 coverage view:** a 24-hour timeline, coverage % and gaps, in the church's timezone.
Live updates through server-sent events, with polling as a fallback.
- **Commitments:** one-off and recurring slots, change or cancel your own, optional reminders
by **email or WhatsApp**, a voluntary "I prayed" marker clearly shown as self-reported.
- **Booking rules** (defaults decided 2026-11-03; the church can change them):
| Rule | Default |
|------|---------|
| Slot length | **1 hour** (church can choose 15, 30 or 60 minutes) |
| Book ahead | Up to **12 months** in advance |
| Recurring bookings | Daily or weekly, for up to **12 months from the day of booking**. Before it runs out the member gets an email/WhatsApp to renew it for another year |
| Cancelling | Up to **8 hours before** the slot. Within 8 hours the slot can't be cancelled in the app (the member is shown who to contact) |
- **Anonymous:** public views show coverage only, never names. Admin access to bookings is
permission-controlled and goes in the access log.
- **Prayer requests:** private by default (submitter and prayer team only). Public or group
prayer needs an explicit choice. Moderation (approve, redact, remove) and a safeguarding
escalation route. Never used for analytics or AI.
- **Campaigns and testimonies:** time-bound campaigns with collective progress (no rankings),
testimonies moderated before publishing.
### Other modules (each can be switched off by the church)
Announcements, devotionals, reading plans, sermons (shared with the Website where both exist),
connect groups, pastoral care (restricted access, audited), serving opportunities, giving links,
an opt-in directory, and **events** (simple events plus the feed, §4).
**Every module is available to every church.** Pricing follows church size (§10), not features.
**Notifications are email and WhatsApp only** (decided 2026-11-03), using the same email and
WhatsApp setup as Cross Events. No push notifications. Members choose their channel per kind of
message (prayer reminders, announcements).
### Roles
Member, Prayer coordinator, Content admin, Pastoral care, Church admin. Public content can be
browsed without an account. Personal actions need sign-in through Accounts.
---
## 8. Servers and deployment
**PM2 and nginx, like Cross Events today.** No Docker.
- **Per church:** one PM2 process (or front/back pair) per product, one PostgreSQL database and
database user per product, its own `.env`, secrets, uploads folder, logs and backups. Names
carry the church slug (`hopefamily-website`, `hopefamily_website` DB, …).
- **Install and update scripts** for each product, built from the Cross Events ones: fresh
install, then a tagged update with a database backup, migrations, build, restart and roll back
on failure. An `add-church` script runs them for the products a church has bought and prints
the nginx config and DNS records.
- **Domains:** `www.`, `events.`, `app.` and `accounts.` on the church's own domain, or on
`<church>.crosscode.co.za` for the Cross Code address (a wildcard DNS record and certificate
for `*.crosscode.co.za` subdomains, so no DNS work per church). Certificates through certbot. Only configured domains are served (no trusting arbitrary Host headers).
- Every product has `/health` (version, DB, jobs, last backup), reported to the Hub.
- Several churches can share a server at first. Any church can move to its own server later
without code changes. The Hub stays on a separate server from church installs.
### Repositories
| Repo | What |
|------|------|
| `joshua/hope-events` | Cross Events (unchanged home) |
| `joshua/events-hub` | Cross Code Hub (renamed in the UI; repo name can stay) |
| `joshua/crosscode-platform` (new) | Monorepo: `apps/accounts`, `apps/website`, `apps/app`, `packages/contracts`, `packages/ui` |
| `joshua/crosscode-site` | The marketing site. The Church Website and Church App move from "coming later" to their own pages when they ship |
**Stack:** the same as Cross Events (Next.js, Express, TypeScript, Prisma, PostgreSQL, pg-boss,
Playwright, Jest), so code and habits carry over.
---
## 9. Build order
| Phase | Version | What | Why this order |
|-------|---------|------|----------------|
| 0 | contracts 1.0.0 | `crosscode.event/1`, `crosscode.church/1`, `crosscode.identity/1`, Hub protocol v3: schemas, docs, sample payloads, the monorepo set up | Everything else depends on them |
| 1 | **Hub 2.0.0** | Cross Code Hub: products, licence per product, per-customer billing with bundles, protocol v3 (v2 still accepted), sibling discovery, AI usage per product | Must exist before a second product is sold |
| 1b | **Cross Events 2.2.0** | **Authenticator app 2FA** (TOTP) next to email and WhatsApp, with recovery codes; admins can see which method each staff account uses | Independent and useful now; the Accounts migration later carries it over |
| 2 | **Cross Events 2.3.0** | Reports `product: "events"` (protocol v3), **Linked products**, the event feed (publish + show events from siblings) | Small; ready for when the Website exists |
| 3 | **Accounts 1.0.0** | OIDC provider, sign-in, 2FA (email, WhatsApp, authenticator app), password reset, consent and notice, export / delete fan-out, admin, install script | The Website is built on it from day one |
| 4 | **Church Website 1.0.0** | Public site, admin, simple events + feed, Hope Family Church as the first church, then a second test church to prove it repeats | More churches need a website than an app; it also covers the "website only" case |
| 5 | **Cross Events 3.0.0** | Sign-in moves to Accounts; user migration keeping password hashes; migration guide | Breaking change, so a major version, after Accounts is proven on the Website |
| 6 | **Church App 1.0.0** | Foundation (PWA, branding, Accounts), House of Prayer, announcements, devotionals, events (simple + feed) | Starts with shared users and the feed already in place |
| 7 | Church App 1.x | Reading plans, sermons, groups, pastoral care, serving, giving links, directory | After House of Prayer is in use |
Each phase adds help content, audit log entries, tests, a CHANGELOG entry and updates to the
docs. Branch → merge → tag → push per feature, as before.
### Release checks (before selling a product)
- Two churches installed from the same tagged release, with separate databases, secrets and uploads.
- Church A's admin can't read or change Church B's data through direct API calls.
- Stopping one church's install, or the Hub, leaves the others running.
- A feed source going down: its last events keep showing, the other sources keep working.
- Signing in once works across all of a church's products; deleting an account reaches every product.
- A database backup is restored successfully.
---
## 10. Pricing
Aimed at small and mid-sized South African churches: the Website and App start well below Cross
Events, since they replace things churches often get cheaply or free today (a basic website,
WhatsApp groups). Prices are rand per month. Decided by Joshua on 2026-11-03.
When the Website and App go on sale, they go in `crosscode-site/src/data/pricing.mjs` and the Hub's
`frontend/src/lib/tiers.ts`.
**The same rules as Cross Events apply to every product:** one month free, yearly billing = 10
months' price, founding churches 35% off for as long as they stay, bundles **15% off** two
products and **25% off** all three, and the tier is checked once a year at renewal. **Add-ons**
(address and storage) are not discounted.
### The church's address: three options
One address covers all of a church's products (`www.`, `events.`, `app.`, `accounts.`), so it's
charged **once per church**, not per product.
| Option | Price | What it means |
|--------|-------|---------------|
| **Own domain** | R0 | The church already has a domain (e.g. `hopefamily.co.za`). They, or whoever manages it, add the DNS records we send. We look after the certificates |
| **Cross Code address** | **+R10 / month** | For churches that don't want a domain of their own: `hopefamily.crosscode.co.za`, with the products at `events.hopefamily.crosscode.co.za`, `app.hopefamily.crosscode.co.za` and so on. Can move to a domain of their own at any time (old addresses redirect for 12 months). Trials use this address for free |
| **Domain included** | **+R25 / month** | We register a `.co.za` or `.org.za` **in the church's name**, renew it every year and manage the DNS. Other endings (`.com`, `.church`, `.org`) are charged at cost on top. If the church leaves, the domain is transferred to them, free |
### Church Website: one price plus storage
| | Price |
|--|------:|
| **Church Website** (5 GB of uploads included) | **R99** |
| Founding (35% off) | R64 |
| **Extra storage** add-on | **+R20 / month per extra 5 GB** |
Uploads are photos, documents and sermon notes. Sermon audio and video are linked (YouTube,
Spotify, SoundCloud…) rather than uploaded, so most churches never need extra storage. Admins see
how much they've used in the website's settings and get an email at 80%. Storage is counted per
church and reported to the Hub, which bills the add-on.
### Church App: on active members
Billed on **active members**: accounts that used the App in the last 90 days, reported to the Hub
as a band (like event income for Cross Events). Visitors browsing without signing in never count.
| Tier | Active members | Price | Founding |
|------|----------------|------:|---------:|
| Mustard Seed | Up to 100 | **R99** | R64 |
| Small Group | 101 – 300 | **R249** | R162 |
| Congregation | 301 – 750 | R399 | R259 |
| Gathering | 751 – 2 000 | R649 | R422 |
| Multitude | 2 001 – 5 000 | R999 | R649 |
| Network | Over 5 000 | From R1 500 | 35% off |
### What churches would pay (no founding discount)
| Church | Products | Products after bundle | + address | Per month |
|--------|----------|----------------------:|----------:|----------:|
| Small, website only | Website | R99 | own domain | **R99** |
| Small, website only | Website | R99 | Cross Code address | **R109** |
| Small, website only | Website | R99 | domain included | **R124** |
| Small, website + app | Website + App (≤100): R198 | R168 (15%) | domain included | **R193** |
| Small, everything | Events Seed + Website + App (≤100): R548 | R411 (25%) | domain included | **R436** |
| Mid-sized, everything | Events Grow + Website + App (301–750): R1 348 | R1 011 (25%) | domain included | **R1 036** |
(Rounded to the rand.)
---
## Decisions (2026-11-03)
| # | Question | Decision |
|---|----------|----------|
| 1 | Separate infrastructure | **Yes:** one install and database per product per church, no shared databases, Hub not on the runtime path |
| 2 | Shared users | **A separate Accounts service per church** (OpenID Connect), used by every product. Roles stay per product |
| 3 | Docker | **No.** PM2 and nginx, like Cross Events |
| 4 | Simple events in the Website and App | **Yes.** Only Cross Events takes registrations and payments. (The app spec's "no event creation in the App" is overruled) |
| 5 | Event sync | Any direction, chosen per install ("Publish my events", "Show events from"). One shared format, `crosscode.event/1`. Only originated events are published |
| 6 | Domains | `www.`, `events.`, `app.` (and `accounts.`) on the church's own domain |
| 7 | Hub | Becomes **Cross Code Hub**: every product's licences, stats, AI and billing in one place, still read-only toward installs |
| 8 | Stack | Next.js + Express + Prisma + PostgreSQL + pg-boss, as Cross Events (not the specs' React + Vite, Redis + BullMQ, Socket.IO) |
| 9 | Pricing | Income bands per product, bundles 15% (two) / 25% (three); every App module for every church (not the website spec's Starter / Growth / Pro tiers) |
| 10 | Hub provisioning | Scripts Joshua runs; the Hub records installs but never deploys or rolls back (not the specs' automated provisioning) |
| 11 | Installs per product | **One install of each product per customer**; the Hub refuses a second licence for the same product |
| 12 | House of Prayer defaults | 1-hour slots, book up to 12 months ahead, recurring bookings for up to 12 months from the day of booking, no cancelling within 8 hours of the slot |
| 13 | App notifications | **Email and WhatsApp only.** No push notifications |
| 14 | Accounts visibility | Hidden from members (branded as the church's sign-in). Only admins and the account holder see account pages |
| 15 | 2FA | Add an **authenticator app** (TOTP, with recovery codes) next to email and WhatsApp: in Cross Events 2.2.0 first, then in Accounts |
| 16 | Pricing | Website **R99** with 5 GB, extra storage as an add-on in steps. App **R99** (up to 100 members), then **R249**. Address once per church: own domain R0, **Cross Code address R10**, **domain included R25**. Extra storage R20 per 5 GB; App tiers above 300 members R399 / R649 / R999 / from R1 500. Add-ons aren't bundle-discounted (§10) |
## Still open
Nothing. When the Website and App go on sale, put the §10 prices in
`crosscode-site/src/data/pricing.mjs` and the Hub's tiers (the Website and App move from "coming
later" to their own prices).