AlpineClubBookingsNZ v0.12.1
AlpineClubBookingsNZ v0.12.1
Release target: v0.12.1 on 2026-07-19. This patch public reference release
brings all changes landed after v0.12.0 into one supported tag. The version
is a deliberate patch bump chosen by the owner even though the range carries
feature work, because every addition is additive and flagged off by default —
nothing changes for an existing club until an admin opts in. The canonical
detailed entries remain in CHANGELOG.md; this page is the release/operator
overview.
Highlights
- Optional sign-in methods behind a new admin Login & Security page
(/admin/security): a per-club password-complexity policy (minimum length
8–64, default 12; four character-class toggles, default off; fixed 128
maximum) enforced only at password-set time, plus two module-flagged
sign-in additions that never replace password login — email magic-link
(single-use hashed token, club-set TTL) and Google OAuth (profile-initiated
account linking only, resolving by pinned Google subject id, never by email
match or auto-provisioning). Both new modules default off; an
un-configured club is byte-identical to today. - Per-age-tier membership billing: membership types gain a Required based on
age tier subscription behaviour that defers to the existing per-tier
subscriptionRequiredForBookingflag (billing liability fixed by age at the
start of the financial year), and annual membership fees gain the same
flat vs per-tier shape the joining fee already carried (existing rows are
the flat fallback; per-family fees stay flat-only). The membership-type
editor adds an explicit "N/A (no age)" allowed-tier option. - Fees editor UX: the annual-fee editor replaces free-text Xero Account/Item
inputs with searchable pickers (ACTIVE revenue accounts / sales-capable
items) fed by the existing admin-gated proxy endpoints, falling back to
manual entry on Xero disconnection, surfacing the resolved default account,
and showing the fee-level proration rule — with no billing-math change. - Lobby Display polish: a six-board template pack (four built-ins + a
two-board import bundle) so every display module is exercised, a guided
visual builder at/admin/display/builder(ADR-004) over the unchanged
save contract with the textarea editors retained as Advanced mode and no
schema change, and night-columns rescoped as an honest permanent 3-night
board. The Lobby Display module remains off by default. - A full documentation library: a docs foundation (style guide, audience-first
hub, curated architecture diagrams, a coverage matrix, an advisory link-check
CI workflow, and a screenshot harness), 65 operator guides in
docs/guides/across four batches, and a fifth batch of member-facing
journey guides indocs/user-guide/, each written against the running
seeded app — closing the coverage matrix to zero gaps. - A screenshot-forward
README.mdmarketing front page with reproducible hero
art and a booking-demo GIF harness, its former deep operational content
relocated into the docs it duplicated. No runtime app code changes. - Fixes and hardening: membership-types editor close-on-save + dirty-guarded
header X,/admin/displaydrill-down back-links (with a repo-wide
normalisation), a dollar-quote-aware fix to the blue/green migration
validator's session-clock gate, and agithub/codeql-actionbump to 4.37.0.
Compatibility and operator impact
- Runtime requirements remain Node.js 24 LTS, npm 11+, and PostgreSQL 16.
- The release contains five migrations: all expand/additive, no contract
migration. Four have explicit safety-ledger rows in
docs/BLUE_GREEN_MIGRATION_SAFETY.tsv, allold_code_compatible=yes:20260718130000_add_magic_linkaddsClubModuleSettings.magicLink
(flagged off, constant default) and a new emptyMagicLinkTokentable
with anON DELETE CASCADEFK toMember. The ADD COLUMN is catalog-only
on the cold single-row settings table; the FK on a brand-new empty child
table scans nothing. Additive; safe at any traffic level.20260719120000_add_google_oauthaddsClubModuleSettings.googleLogin
(flagged off, constant default) and a nullableMember.googleSubwith a
unique index. Both ADD COLUMNs are catalog-only (thegoogleSubcolumn
takes no default, so even on the hotMembertable it is metadata-only);
the unique index builds over an all-NULL column (NULLs never collide) and
briefly blocksMemberwrites — switch toCREATE UNIQUE INDEX CONCURRENTLYifMemberis very large. No backfill, no provider call.20260719130000_add_based_on_age_tier_subscription_behaviorregisters the
BASED_ON_AGE_TIERvalue on theMembershipTypeSubscriptionBehaviorenum
(ALTER TYPE ... ADD VALUE IF NOT EXISTS). Purely additive; no existing
row changes and no old colour reads the new label before cutover.20260719140000_annual_fee_age_tieradds a nullableMembershipAnnualFee.ageTier
(no default, no backfill — every existing row stays the flatNULL-tier
row and resolves identically) plus index/constraint reshaping on the cold
MembershipAnnualFeeconfig table: the uniqueness re-keys from
(membershipTypeId, effectiveFrom)to(membershipTypeId, ageTier, effectiveFrom)with a partial unique flat index, the effective-lookup
index widens to includeageTier, and the single per-type GiST no-overlap
EXCLUDE is split into two partial constraints (flat-vs-flat and same-tier
overlaps still conflict; flat-vs-tier and cross-tier coexist). A raw-SQL
CHECKforbids per-tier PER_FAMILY rows. Catalog-only ADD COLUMN plus
brief-lock index/EXCLUDE swaps on a cold table; no DML.
- The fifth migration,
20260718120000_add_login_security_setting, creates the
empty single-rowLoginSecuritySettingconfig table (password policy plus the
magic-link TTL) with no FK and no hot-table or breaking SQL, so it needs no
ledger row — the same policy under which several v0.12.0 additive migrations
carried no row. An absent row falls through to the code defaults (minimum
length 12, classes off, TTL 15), so an un-configured club behaves exactly as
before. - One old-colour operator caveat. Because the old colour's annual-fee
resolver does not filter by age tier, a per-age-tier annual-fee row is not
invisible to it — once such a row falls in an active window the old colour can
select it for a member of any tier (first match byeffectiveFromdesc) and
mis-price them at the wrong tier's amount. Do not create per-age-tier
annual-fee rows until the cutover completes. Flat annual fees and existing
rows are unaffected; per-tier joining fees already shipped in v0.12.0. - The two new sign-in modules default off, so magic-link and Google sign-in
stay disabled through the migrate→cutover window until an admin enables the
module (and, for Google, a member links their account). The
password-complexity policy applies only at password-set time and never
re-validates an existing password. - No migration makes a Xero, Stripe, or SES call, and no member is re-grouped
in Xero. The annual-fee Xero pickers read only the existing admin-gated
proxy endpoints server-side; no Xero secret reaches the browser bundle.
Upgrade and verification
Follow docs/UPGRADING.md from v0.12.0 to v0.12.1. In summary:
restore-test a fresh backup, review the five pending migrations against the
safety ledger, deploy in a normal window (no contract window is required this
release), and — critically — do not author any per-age-tier annual-fee rows
until cutover completes.
After cutover, verify at minimum:
- Login & Security — the admin security page shows the intended password
policy (an un-configured club still accepts and rejects passwords exactly as
before), and existing members can still sign in with their password. - Optional sign-in modules — magic-link and Google OAuth remain disabled
under Admin > Modules unless the club intends to use them; if enabling
Google, per-clubGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETare configured
perCONFIGURATION.mdand a member can link and then sign in. - Per-age-tier billing — for any membership type set to Required based on
age tier, the age-tier settings drive which tiers are billed, and exempt
tiers get no subscription invoice;REQUIRED/NOT_REQUIREDtypes are
unchanged. - Annual fees — admin fee configuration and the public annual-fees embed
show the same amounts as before; per-tier annual-fee rows are added only
after cutover. - Fees editor — the annual-fee Account/Item pickers list the expected Xero
codes (or fall back to manual entry with the amber notice if Xero is
disconnected), and the surfaced default account is correct. - Lobby Display — the module stays off unless intended; if enabled, the
template pack and the guided builder at/admin/display/builderwork, and
guest phones stay hidden unless both the member and the lodge opt in. - Docs and README — the operator/member guides and the new README render
correctly; these are documentation-only and carry no runtime risk.
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.