AlpineClubBookingsNZ v0.13.2
Release target: v0.13.2 on 2026-07-23. This patch public reference release
brings all changes landed after v0.13.1 into one supported tag. It carries
five operator-relevant workstreams, one of which needs deliberate handling at
deploy time:
- The club-theme orphan-column expand→contract pair (#2187). The theme
rebuild dropped from seven hand-picked brand colours to three seeds, leaving
four formerClubThemecolumns dead to code. This release ships both the
additive expand migration that lets the new runtime stop naming them and
the destructive contract migration that drops them — so the contract half
needs the blue/green handling described below and is legal only once the new
runtime has drained the previous colour. - Config-bundle format version 2 (#2187). Configuration bundles now export
at format version 2; a bundle exported by an older app (version 1) is refused
on import with a clear message rather than importing stale colour columns. - Guided provider setup wizards for Xero, Stripe, and Google sign-in
(#2080/#2081/#2082/#2087). Provider credentials, already DB-only since
v0.13.1 (#2079), are now captured through step-by-step in-app wizards. - The opt-in AI help assistant (#2094). A new, default-off, spend-metered
module that answers members' free-text questions grounded strictly in each
page's curated help. - Backup configuration re-homed to the encrypted store (#2095). The runtime
now reads backup settings only from the in-app credential store; the
legacyBACKUP_*environment variables are no longer honoured, and an
unmigrated legacy config makes the nightly backup report FAILURE until the
operator re-enters credentials in the new backup settings UI.
The canonical detailed entries remain in CHANGELOG.md; this page is the
release/operator overview.
⚠️ TheClubThemeorphan-column drop is destructive and must not run while a
pre-#2187 colour is still draining. The four dropped columns
(brandCharcoal/brandRidge/brandMist/brandSnow) were named in the
SQL of every colour up to and includingv0.13.1— Prisma projects every
scalar of a model in an unnarrowedSELECTand in a mutation's implicit
RETURNING. Dropped whilev0.13.1is still the draining colour, the contract
migration breaks the old colour's theme reads and writes. Thev0.13.2runtime
names none of them, which is what makes the drop legal — but only after
v0.13.2has replaced and drainedv0.13.1. Either defer the contract drop
until then (see below) or run it in a later quiet window with the breaking-
migration override. If the previous colour is still live, do not run the
drop.
Highlights
Club theming: three-seed substrate and the orphan-column drain (#2187)
- Site Style now derives the whole theme from three seed colours instead of
seven hand-picked ones. A vendored Radix colour generator turns one required
accent plus two optional seeds into the full light/dark 12-step palette, with
cross-colour text contrast guaranteed by construction, and the whole member and
admin app — plus the public website — now paints from that generated palette. 20260722140000_expand_club_theme_orphan_column_defaults— EXPAND
(additive, blue/green safe). Adds a DBDEFAULTto each of the four former
columns so the new runtime canINSERTaClubThemerow without naming them,
while the draining previous colour (which still writes explicit values) is
unaffected. No data is rewritten and no column is dropped.20260722160000_contract_drop_club_theme_orphan_columns— CONTRACT
(destructive). DropsbrandCharcoal/brandRidge/brandMist/
brandSnow. The four surfaces are derived from the generated substrate at
render time (deriveBrandShims), so there is zero resolvable data loss, but the
drop is irreversible without a restore. It carries a full rationale row in
docs/BLUE_GREEN_MIGRATION_SAFETY.tsv
(previous_expand_release = 20260722140000_expand_club_theme_orphan_column_defaults,
old_code_compatible = yes) and refuses to run without the breaking-migration
acknowledgement.- Configuration bundles move to format version 2. A bundle exported by an
older app (version 1) is refused on import with a clear message rather than
importing stale colour columns.
Guided provider setup wizards (#2080, #2081, #2082, #2087)
- Xero (#2080/#2081). A reusable wizard shell walks a Full Admin through app
creation, credential capture, and the OAuth connect, then completes setup with
verified webhooks, item-code mappings, and member import, showing an amber
badge on the setup page while webhook verification still needs attention. - Stripe (#2082). Captures its keys into the encrypted store and reads the
publishable key at runtime, so changing keys takes effect immediately rather
than needing a fresh build. - Google sign-in (#2087). Captures its OAuth credentials in-app, runs on a
request-scoped NextAuth config, and keeps the module locked until a real Google
round-trip verifies, so an unconfigured provider can never render a visibly
broken sign-in path. - In all three, legacy provider env vars are detected, warned about, and ignored
— re-enter the values in the wizard, then remove the env vars.
AI help assistant and the chat-style help widget (#2094)
- The in-app help is now a chat-style widget on every surface (#2209, #2210).
A single curated help corpus feeds a widget that answers page-specific
questions and shows a full page guide, replacing the older scattered help UIs. - The AI help assistant module is opt-in and off by default (#2211, #2212).
With it on, authenticated members can ask free-text questions that a paid AI
model (Anthropic Claude Haiku 4.5) answers grounded strictly in the curated
corpus. The club supplies its own Anthropic API key, held only in the
encrypted vault (never an env var). A hard monthly spend cap (default
NZ$10, integer cents) stops AI answers for the rest of the month once
reached; the budget gate fails closed, reserving a conservative worst-case
cost before each call and refusing to spend if it can no longer record usage.
Enabling the module without a key is harmless — the ask box degrades to a
structured fallback and curated page help still works. Members are told the
answer may be wrong and that their question is sent to Anthropic (United
States); the question text itself is never stored.
Backup configuration re-homed to the encrypted store (#2095)
- Backup settings move into the encrypted credential store and out of the
environment. The S3 access key and secret, destination bucket and region,
retention window, restore-validation shadow database, and the on/off switch are
managed from a new Admin → Integrations → Database Backups page
(/admin/backups), with a Run backup now action that runs as a background
job behind a database-level cross-process lock. The S3 secret and the
restore-validation connection string are write-only and never echoed back. - The legacy
BACKUP_*environment variables are no longer read. An install
that configured backups through the old env vars upgrades to an empty store, so
until the operator re-enters the backup settings the nightly backup reports a
loud FAILURE rather than silently skipping — a stopped disaster-recovery path
must alert, not disappear. OnlyBACKUP_CRON_SCHEDULE(cron-leader timing —
when the nightly job fires) stays in the environment.
Hardening and fixes
- Config-transfer column audit (#2178). A reverse-drift guard fails the build
if a club-settings column is neither exported nor explicitly excluded,
complementing the singleton-table audit (#2200). - Season-subscription charge-coverage reconcile (#2179). The guard that flips
a staleNOT_REQUIREDseed subscription row now checks for an active
charge-coverage claim (both at read and, relationally, at write) instead of a
null-check on a to-many relation that was always true, so the reconcile stands
aside when live coverage exists and proceeds when it does not. - Dependency advisories (#2196, #2224). Cleared newly published advisories in
hono,fast-uri,dompurify, andsharp, and moved Next.js to 16.2.11 to
clear a middleware-bypass and a Server-Actions denial-of-service advisory.
Compatibility and operator impact
- Runtime requirements remain Node.js 24 LTS, npm 11+, and PostgreSQL 16.
- The release contains six migrations: five additive/expand migrations plus one
destructivecontractmigration.- Expand (no operator action):
20260722120000_add_integration_wizard_progress(the wizard cursor for the
guided setup shell, #2080),20260722130000_add_xero_webhook_validation_receipt
(the Xero webhook validation receipt sink, #2081),
20260722140000_expand_club_theme_orphan_column_defaults(the theme
orphan-column defaults, #2187),20260722150000_add_backup_run(the backup-run
ledger, #2095), and20260723120000_add_ai_assistant(the AI assistant models,
#2211). Each is additive and blue/green safe. - Contract (needs handling):
20260722160000_contract_drop_club_theme_orphan_columnsdrops the four dead
ClubThemecolumns. The blue/green validator refuses it without
ALLOW_BREAKING_BLUE_GREEN_MIGRATIONS=1plus a non-empty
BLUE_GREEN_MIGRATION_OVERRIDE_REASON. Because the paired expand migration
ships in this same tag, the contract drop is legal only after the
v0.13.2runtime has replaced and drainedv0.13.1. Either defer it —
mark it applied without running it (prisma migrate resolve --applied 20260722160000_contract_drop_club_theme_orphan_columns) until the expand-
carrying release has soaked, then run it in a later deploy — or run it in
a quiet window with the override once the previous colour is fully drained.
Set the override for that deploy only, then unset it so the next deploy does
not inherit it.
- Expand (no operator action):
- Backups need re-entry immediately after upgrade (#2095). With
BACKUP_*
env vars no longer read, an install that configured backups through them
upgrades to an empty store. Re-enter the backup settings at Admin →
Integrations → Database Backups — until you do, the nightly backup reports a
loud FAILURE (never a silent skip). OnlyBACKUP_CRON_SCHEDULEremains an
environment variable. - Older configuration bundles no longer import (#2187). A bundle exported by
a pre-v0.13.2app (format version 1) is refused on import; re-export from the
upgraded source install to move configuration between installs. - The provider wizards and the AI assistant add no forced action. The
wizards are the new, guided path to the same DB-only credential store already
introduced inv0.13.1; the AI assistant module is off by default and does
nothing until a Full Admin enables it and enters an Anthropic key. - A fresh, restore-tested backup is mandatory before the contract drop. There
is no down-migration: aDROP COLUMNcannot be undone by rolling the app
back, so restore from backup is the only recovery for the dropped columns
(their values are derived at render time, so no theme surface is lost, but the
raw columns are gone).
Upgrade and verification
Follow docs/UPGRADING.md from v0.13.1 to v0.13.2 ("v0.13.1 → v0.13.2"). In
summary: deploy v0.13.2, letting the five expand migrations run; defer the
ClubTheme orphan-column contract drop (mark it applied without running it)
until the new runtime has drained v0.13.1, or run it later in a quiet window
with ALLOW_BREAKING_BLUE_GREEN_MIGRATIONS=1 and a
BLUE_GREEN_MIGRATION_OVERRIDE_REASON recording the soak and a restore-tested
backup id; then unset the override. After the app is healthy, re-enter the
backup settings at Admin → Integrations → Database Backups so nightly backups
resume.
After cutover, verify at minimum:
- Expand migrations applied cleanly and the app is healthy on the new
colour; the contract drop is either deferred (marked applied) or run in its
own quiet window oncev0.13.1is fully drained. - Backups re-entered (#2095). The Backups page shows the durable
destination configured, a manual Run backup now succeeds, and the nightly
run reports SUCCESS rather than the legacy-env FAILURE. - Theme unchanged (#2187). A club with a non-default palette still renders
its colours in light and dark after the orphan-column drop — the four dropped
surfaces are derived from the generated substrate. - Config bundle round-trips. Export a bundle from the upgraded install and
confirm it imports; a pre-upgrade (version 1) bundle is refused with a clear
message. - Provider wizards reachable (#2080/#2082/#2087). Admin → Integrations shows
the Xero, Stripe, Google sign-in, and Backups cards, each opening its wizard. - AI assistant opt-in (#2094). With the module off, the chat-style help
widget answers curated page questions on every surface; enabling the module
and entering an Anthropic key adds the free-text ask box, and the monthly spend
cap hard-stops answers once reached.
This release does not publish an npm package (package.json remains private).
The supported artefacts are the Git tag/GitHub release and the repository's
container-image release workflow.