Skip to content

v0.12.2

Choose a tag to compare

@thatskiff33 thatskiff33 released this 20 Jul 06:48
ff3ba8e

AlpineClubBookingsNZ v0.12.2

Release target: v0.12.2 on 2026-07-20. This patch public reference release
brings all changes landed after v0.12.1 into one supported tag. As with
v0.12.1, the version is a deliberate patch bump chosen by the owner even
though the range carries feature work — most additions are opt-in and flagged
off by default. Unlike v0.12.1, this release is not purely additive: it
lands the first destructive contract migration since the expand/migrate/contract
series began (legacy-structure contraction E13, the blue/green-safe subset of
#1939), a second breaking column-drop migration (Xero member-grouping
multi-select age tiers), and one changed default behaviour (admin post-login
landing). Both breaking migrations require the operator acknowledgement
ALLOW_BREAKING_BLUE_GREEN_MIGRATIONS=1. The canonical detailed entries remain
in CHANGELOG.md; this page is the release/operator overview.

Highlights

  • Production Xero lock-date 503 fix. Retroactive (past-dated) booking
    creation returned a 503 "Could not verify the Xero lock dates" whenever the
    connected Xero organisation actually had lock dates set — the exact case
    the guard exists for. xero-node's serializer coerces an MS-JSON /Date(...)/
    payload into a JS Date, so parseXeroLockDate's value.slice(0,10) threw
    and the guard failed closed; reconnecting could never fix it. The parser now
    accepts string | Date.
  • Xero reliability follow-ups. A lock-date error taxonomy classifies the
    guard's 503 as reconnect_required | rate_limited | transient with
    cause-specific, admin-only reason copy (member responses byte-identical); a
    click-only live connection-health probe (GET /api/admin/xero/status?probe=1)
    replaces the token-row-presence "Connected" chip; the finance-sync date parser
    is now Date-aware so SDK-coerced due-dates no longer drift into the
    no-due-date aging bucket; and the Xero contact-create gate is shrunk to require
    only name + email, with sparse members creating clean payloads.
  • Age-exempt (N/A) membership-type lifecycle. Membership types whose
    allowed-age-tiers list "N/A (no age)" are now the single source for genuinely
    age-exempt members, enforced through one shared helper at every ageTier write
    site. On top of it: bulk membership-type assignment (up to 100 members) from
    the members page with preview tokens, per-member audits, and a batched Xero
    reconcile; Xero Setup import into membership types (or types + age tiers) that
    never overwrites an existing current-season assignment and fully reports what
    it skips; and an opt-in mode that lets subscription paid-detection look through
    to per-type + tier fee item codes (default off = today's behaviour).
  • Xero member-grouping multi-select age tiers. A grouping rule can now target
    any subset of age tiers (empty = all tiers) with specificity-based overlap
    resolution and a fingerprint serializer proven byte-identical to the old one
    for the migrated cases, plus a "Refresh from Xero" button and a "Last synced"
    header. Its migration drops the old scalar ageTier column (breaking — see
    below).
  • Admin post-login landing (behaviour change). After sign-in a member with
    admin access and no set preference now lands on their admin area instead of the
    member dashboard, applied by the application redirect resolver; a nullable
    Member.postLoginLanding enum backs a profile toggle. No open-redirect
    surface; a demoted admin with a stale preference still lands safely on
    /dashboard.
  • Admin and booking UX. A Ctrl/Cmd-K admin feature-search palette; a
    bookings-officer key-card row on the dashboard; a fixed admin booking-calendar
    overflow (auto-expanding rows, "+N more" chip, greyed finished days); the
    /finance and /lodge shells now inherit the club theme; /admin/security
    gains per-section Edit→Save gating, a persistable magic-link TTL, and a
    stale-clobber fix; the member booking-edit panel now renders the required
    minors-without-adult justification field (machine-readable error code); and an
    in-progress stay's minimum-stay rule is evaluated against the whole contiguous
    stay, surfaced as an advisory warning.
  • Legacy schema contraction E13 (destructive). Drops the dead EntranceFee
    and AgeTierXeroAcceptedContactGroup tables and an orphaned account-mapping
    row — the blue/green-safe subset of #1939, re-verified as zero-reader against
    the v0.12.1 tag; the remainder is deferred to #2129/#2130/#2131. The #2130
    runtime-prep also ships here (#2133): explicit selects stop the deployed
    client naming the deferred columns, making next release's contract drop
    blue/green-legal.
  • Docs and tests. The member-facing user guide is mirrored one-way to the
    GitHub wiki by a push-triggered workflow (npm run docs:wiki-sync), and the
    E2E seed fixtures were made relative/never-expiring so the suite stops going
    stale at date boundaries.

Compatibility and operator impact

  • Runtime requirements remain Node.js 24 LTS, npm 11+, and PostgreSQL 16.
  • The release contains four migrations: two expand/additive and two breaking
    contract migrations.
    Both breaking migrations require the operator
    acknowledgement ALLOW_BREAKING_BLUE_GREEN_MIGRATIONS=1 for this deploy — the
    blue/green validator refuses a breaking migration without it. All four carry
    old_code_compatible=yes where they have a ledger row.
    • 20260719170000_xero_grouping_age_tiers_multiselect (breaking contract,
      ledgered
      ) backfills the scalar XeroContactGroupRule.ageTier into a new
      ageTiers array (X → [X], null → [] = "all tiers") and then drops the
      scalar column
      . old_code_compatible=yes but window-bounded and
      admin-only
      : between migrate and cutover the old colour's
      grouping/membership-admin reads still name ageTier and error with
      column-does-not-exist. The live grouping sync fails closed (a re-grouping
      edit errors and is retried post-cutover — no partial write, no money, no
      booking capacity). Deploy with grouping/membership-admin traffic idle and cut
      over promptly. No member is re-grouped in Xero by the migration.
    • 20260720120000_contract_drop_entrance_fee_and_agetier_xero_group
      (destructive contract, ledgered) drops the dead EntranceFee and
      AgeTierXeroAcceptedContactGroup tables and deletes the orphaned
      entranceFeeAmountCents account-mapping row. old_code_compatible=yes: an
      independent drop-proof review re-verified zero readers against the
      v0.12.1 tag
      (the colour draining during this deploy) with no FK/cascade
      trap, so the draining old colour keeps working. The acknowledgement is
      required only because a DROP is breaking by class. Deliberately
      kept/deferred (still read by the current runtime): the EntranceFeeCategory
      enum, SeasonRate (the live public {{hut-fees}} embed), MembershipTypeAgeTier,
      and the XeroItemCodeMapping.isMember / AgeTierSetting.xeroContactGroup*
      columns — follow-ups #2129/#2130/#2131.
    • 20260719150000_add_post_login_landing (expand, ledgered) adds a
      PostLoginLanding enum plus a nullable Member.postLoginLanding column with
      no default — a metadata-only catalog change even on the hot Member table.
      No existing member's landing changes on migrate; the changed admin default is
      applied entirely by the application redirect resolver.
    • 20260719180000_add_use_fee_schedule_item_codes (expand, ledger-exempt)
      adds a single flagged-off boolean on the cold single-row
      MembershipLockoutSettings table — additive with a constant default, so it
      carries no ledger row under the same policy as v0.12.1's
      add_login_security_setting.
  • The one behaviour change is admin post-login landing: from the first login
    after cutover, a member with any accessible admin area (including read-only
    admins and finance-only viewers) lands on their admin area instead of
    /dashboard, unless they set the new profile preference. It is applied by the
    application, not by stored data.
  • Opt-in, no behaviour change until enabled: the new "use membership fee item
    codes" subscription paid-detection mode (its migration only adds the flag), and
    the age-exempt (N/A) membership types (config-only, no migration — effective
    only when an admin sets a type's allowed age tiers to include or restrict to
    N/A).
  • No migration makes a Xero, Stripe, or SES call, and no member is re-grouped in
    Xero by any migration.

Upgrade and verification

Follow docs/UPGRADING.md from v0.12.1 to v0.12.2. In summary: take and
restore-test a fresh backup (mandatory this release — the E13 DROP TABLEs are
irreversible without it
), review the four pending migrations against the safety
ledger, set ALLOW_BREAKING_BLUE_GREEN_MIGRATIONS=1 for the deploy, run the
grouping-drop deploy in a quiet admin window and cut over promptly, and complete
the post-upgrade actions.

After cutover, verify at minimum:

  1. Xero lock dates — creating a retroactive (past-dated) booking no longer
    returns the 503 when the org has lock dates set; the admin Xero page shows the
    real connection-health chip (and the Reconnect CTA when a token is revoked).
  2. Member-grouping rules — each former single-tier rule shows that one tier
    and each former "Any age" rule shows "all tiers"; "Refresh from Xero" triggers
    no unexpected full regroup.
  3. Post-login landing — admins (including read-only and finance-only) land on
    their admin area on the first sign-in after cutover unless they set the new
    profile preference; plain and demoted members still land on /dashboard.
  4. Membership tooling — the members-page bulk "Set Membership Type" flow and
    the Xero Setup import mapping modes work and never overwrite an existing
    current-season assignment; the opt-in fee item-code mode is off unless enabled.
  5. Age-exempt types — a type whose allowed tiers restrict to or include N/A
    resolves holders to NOT_APPLICABLE, and N/A members stay non-bookable as
    linked guests.
  6. Booking edit — the member edit panel renders the required justification
    field for a minors-without-adult edit; an in-progress stay can be extended
    night-by-night with minimum-stay evaluated across the whole contiguous stay.
  7. Legacy contraction — the deploy applied the E13 drops cleanly and the app
    is healthy (the dropped tables had no readers).

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.