- §15 AI helpers (2.0): one-button helpers (draft an event, summarise
reports and feedback, draft messages, improve text) on Claude Haiku 5.5,
called through the Hub, which holds the key, meters usage in rand and
enforces a monthly cap per licence; admin usage view; POPIA notes
- §16 AI later stages (unversioned): Ask AI, help/navigation assistant,
actions with confirmation, more helpers, long-term self-hosted AI
- Hub: AI cap as the first per-licence limit, AiUsage model; decisions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
- Retention policies (Admin -> Privacy -> Retention) applied by a nightly
`retention` job: inactive accounts anonymised after 5 years with a
30-day warning (on by default; never staff roles or people with an
upcoming registration), form answers, login history, payer details in
payment notifications, audit/access logs
- User.lastLoginAt (set on login, backfilled) and inactivityWarnedAt
- Breach register (SecurityBreach) with drafts for the people affected
and the Information Regulator
- Readiness checklist with a score
- Children's/medical flag in the access log; {retention} placeholder in
the privacy notice
- Unit tests (retention.test.js), e2e (privacy-retention.spec.ts); docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
- Admin -> Privacy (POPIA): edit the privacy notice (built-in text as the
start, placeholders for org/IO/operators), drafts, preview, numbered
versions; earlier versions readable on /legal/privacy?version=N
- "Significant change" versions are accepted once after login
(NoticePrompt); sign-up records the version accepted
- Operator register (seeded with the services the app uses), listed in
the notice via {operators}
- Consent log (news opt-ins, notice acceptances, parental consent) and
access log (report exports, registers, certificates, group lists, form
answers, consent/medical lists, backups, impersonation)
- Information Officer on the contact page
- Unit tests (privacyNotice.test.js), e2e (privacy-notice.spec.ts); docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
- Privacy page: news opt-in per channel with history, download my data
(JSON + PDF), correction and deletion requests
- Bulk messages are an Event update or News & announcements; news only
reaches people who opted in, with unsubscribe links, one-click
List-Unsubscribe headers and "Reply STOP" on WhatsApp
- WhatsApp STOP via WAWP's message webhook, checked against a secret in
the webhook URL
- Sign-up opt-in box; existing accounts kept opted in and asked once
- Admin -> Data requests with 30-day due dates and a daily reminder job
- Deleting an account is a request; anonymising now covers attendees,
form answers, consent details, payer details in provider payloads, etc.
- Unit tests (privacy.test.js) and e2e (privacy.spec.ts); docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
- scripts/deploy.sh with no tag deploys the latest main (detached
origin/main); with a tag, that release.
- ecosystem.config.js is no longer in git (now ecosystem.config.example.js;
the deploy creates the install's copy from it once). The deploy keeps the
install's copy safe across checkouts and rollbacks, including releases
that still tracked it.
- The script runs from a temp copy, so checking out a new deploy.sh can't
change it mid-run; set -E so failures in functions still roll back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deploy's next build fails on react/no-unescaped-entities. Bump to
1.37.3; the handover now says to lint the whole frontend before tagging.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- src/payments/status.js: open checkouts, checkCheckouts and the
payment-status job (every 15 min, checkouts 10 min - 3 days old). Found
payments are settled exactly as their webhook would be (settlePayment,
extracted from the webhook handler: amount check, record, emails).
- PayFast: signed API client (alphabetical signature, testing=true in
sandbox), findPayments from the transaction history CSV matched on
M Payment ID, and testConnection (signed /ping). Verified against a
real sandbox account.
- Yoco documents no checkout lookup, so its checkouts are "can't check".
- Payments -> Reconcile: "Waiting for payment" with Check status /
Check all; PayFast card: Test connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shared useOnlinePayments hook (GET /api/payments/providers) gates the
registration success page, My Events (Make payment, Make donation), the
donate page, Sponsor a spot, the register form's donation box and the
supervisor Payment Link tab; they explain why where it helps.
Roadmap: SnapScan and Zapper dropped; later versions move up by two.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PayFast (custom integration): signed form (PHP-style encoding, checked
against a real sandbox), ITN checks (signature over all posted fields,
PayFast host, VALID from /eng/query/validate, amount vs checkout).
- ProviderCheckout records every checkout (amount, metadata, request);
webhooks match payments to it and hold a different amount for review
(ProviderTransaction.note).
- /pay/<id>: payers choose a provider when several are on, and the
browser posts PayFast's form from there, so payment links work for
every provider.
- Site settings → Payments: PayFast card (merchant ID/key, encrypted
passphrase, sandbox/live) while PayFast is enabled.
- Payer-facing wording no longer names Yoco; legal pages name both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- src/payments: provider registry and PaymentProvider interface
(createCheckout, verifyWebhook/parseWebhook, getTransaction, refund,
listTransactions as capabilities), Yoco as the first provider. All four
direct Yoco checkout calls go through payments.createCheckout.
- Webhook split into the provider part (signature, parsing) and shared
processing; same URL (/api/webhooks/yoco), same behaviour.
- YocoTransaction renamed in place to ProviderTransaction (+ provider,
unique per provider + externalId); Payment.provider added and
backfilled to "yoco". Payment.method stays the wallet type so cashups
are unchanged.
- Site settings → Payments: Enabled toggle and order per provider; Yoco's
settings shown while it's enabled. R15 minimum for all online payments.
- New endpoint names (/api/payments/checkout, /api/provider-transactions);
the old /yoco-checkout and /yoco-transactions paths still work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Waitlist per session: entries for pick-sessions tickets name a session;
offers use that session's free places, never run past its start, and
claim links tick it. Held offers count towards session capacity.
- At the door: the Select items pop-up has the session picker, with
today's session preselected for new attendees.
- On pick-sessions events every ticket shows the session list straight
away. "All sessions" tickets record the ticked sessions
(RegistrationOption.sessionIds) at one price; scanning, attendance,
certificates, reminders and capacity follow them.
- Fix: per-session stock limits weren't enforced (takenCount filtered
WaitlistEntry by a sessionId column that didn't exist, and the error
was swallowed), and the public event page lost its stock info.
- Fix: editing a door registration with picked sessions couldn't be saved.
- Fix: the offline-scanning service worker served /_next/static cache-first
forever, so dev browsers that had opened the scanner ran stale code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Event wizard: how people book (whole course / pick sessions), capacity
per session and per-option session mode. Session picker on the public
registration page, Manual registration and the kiosk; at the door points
to Manual registration. Register, tickets and My Events handle picked
sessions. Adds docs/ROADMAP-v2.md and docs/HANDOVER.md so work can carry
on from another machine, and a pick-sessions e2e test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Options on a multi-day event can be bought per session (one line and
ticket per session, priced per session, bookable until the session
starts). Sessions can have a capacity covering picked places and
whole-course tickets. Picked tickets only scan on their own session; the
register, certificates and reminders follow the sessions booked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Timed jobs move from in-process timers to a job registry run by pg-boss:
once per schedule across processes, retries, run history, cron schedules
in the church timezone, and an admin Background jobs page with Run now.
Playwright end-to-end tests run against their own seeded database. A
Gitea Actions workflow runs backend tests, frontend checks and the e2e
tests, and deploys v* tags with scripts/deploy.sh once secrets are set.
Also fixes the at-the-door check-in count for multi-day tickets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Event wizard: one date or multiple sessions with a repeat-pattern builder,
and a new Attendance step (late joining and pricing, check-in window,
session reminders, certificates with preview). Public pages show the
schedule and late prices; the scanner picks today's session; new
attendance register page, My Certificates, and a public verify page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can run over several sessions: per-session check-in with a whole-day
or session-time window, late joining with pro-rata or fixed late pricing,
an attendance register (Excel/PDF), session reminders, and completion
certificates (four templates, public verify page).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Event cards on the home page and events list get a diagonal "Early bird"
ribbon in the top-right corner while an early-bird tier is running and
registration is open. GET /api/events returns earlyBirdEndsAt per event.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>