Update — 2026-08-28
28 August 202628 August 2026
Safer member links
- Portal sign-in and account-activation links now open a confirmation page
without consuming the one-time credential. The credential is used only when the member deliberately continues, preventing email security scanners from invalidating a valid link before the member opens it.
- Confirmation pages prevent caching and referrer leakage, and malformed,
expired or replayed credentials still fail closed.
More predictable learning pathways
- Opening a member's learning pathway is now read-only. Any LMS completion
synchronization is performed through an explicit protected write, while the page still updates automatically when it detects that the recorded pathway and completed course evidence differ.
- Portal course purchases, packages and progress writes now honour the active
member's module, programme and current-stage access before payment or progress state is created. Existing subscriptions remain visible when access changes so a household can still manage or cancel them, including archived packages that remain billable. New and temporarily paused members now use the same canonical status rules as the rest of the member portal.
- Demo clubs can continue to view published forms without creating persistent
responses, members, activity or automation records.
Clearer feature access and assessment controls
- Smart Planning and Video Reviews are now enforced by the server as well as
hidden in the interface when a club does not have the module.
- The Register page now hides its lesson-plan card when Smart Planning is not
enabled, so it no longer advertises an action the server will refuse.
- Assessment scoring now combines colour with cross, warning and check icons,
accessible names and selected-state announcements. Touch controls remain comfortably sized, while the desktop matrix stays compact.
- Form Builder dialogs now trap keyboard focus correctly. Smart Planning's
mobile steps show overflow cues and keep the active step in view, member lists sort consistently by surname, and several small mobile actions have larger tap targets.
More truthful public and operational states
- Returning from GoCardless no longer claims that setup succeeded before the
provider webhook confirms it.
- The enrolment-complete screen now proves the completed enrolment from the
server before showing success, and no longer places member names or internal identifiers in its URL.
- Active clubs are redirected away from the suspended-billing screen, and a
future trial end is no longer described as an expired trial.
- Email health no longer reports “ready” when the Resend domain check is
unavailable or the domain still needs attention.
Faster, safer staff data loading
- Returning to the Households directory now restores the last same-club result immediately and
refreshes it in the background. If that refresh fails, the previous result stays visibly marked as older instead of disappearing or looking current.
- Once the accompanying Household read-model SQL is applied, large Household directories load in
exact server pages instead of transferring the whole club at once. Search, filters, summary counts and both member-assignment pickers remain complete across every page; malformed or failed reads show as unavailable rather than an empty family list.
- Member directory refreshes now update one stable same-club cache entry instead of retaining a
separate whole-directory payload after every profile edit. Confirmed member and household changes continue to request fresh server data, and switching clubs still clears the session cache.
- The public website now lives at
/, while signed-in club operations have a
dedicated /dashboard address. Sign-in, onboarding and staff navigation all use the same destination without adding a second navigation shell.
- Staff startup now uses one guarded server snapshot for club access, role,
modules, terminology and shared reference data instead of a browser fan-out. Explicit club switching remains separately authorised.
- Dashboard membership, programme-lens membership, Today, attention and
contactability data now revalidate through one guarded request. Selected programmes are proved against the signed-in club and incomplete lens reads fail closed; optional failures stay visibly unavailable and cannot erase a valid core dashboard or appear as reassuring zeroes.
- Member profiles now make one initial snapshot request instead of immediately
invalidating it and requesting the same data again. Saves and other explicit changes still refresh from the server.
- The dashboard now waits for shared club reference data before calculating its
figures, and an older club or programme request can no longer replace the latest view if it finishes late.
- Dashboard figures are now committed to their exact club and programme before
they can be shown, so switching context cannot briefly display the previous club's totals. Failed reference or dashboard reads stop loading with a clear unavailable message; same-club last-known-good figures remain visibly marked instead of being replaced or cached as believable zeroes.
- Register setup now starts its independent staff, lesson, rank and event reads
together. A failed core read now stops with a retryable unavailable message instead of looking like an empty register; the optional staff picker remains non-blocking.
- Public marketing effects and authenticated staff navigation now load only on
the screens that use them. Once the signed-out home view is resolved, its text is independent of the decorative 3D download; reduced-motion visitors do not download that scene, and background-tab safeguards remain in place.
- The signed-out landing page now keeps a page-shaped fallback while its
separate marketing bundle loads, rather than briefly becoming blank.
- Register, calendar and event opening states now use their matching page
skeletons, making progress clear without briefly showing an empty screen.
More trustworthy high-volume totals
- Member, finance, product-sales, feedback, SMS and portal reports now load every
available page instead of silently stopping at the database API's first 1,000 rows.
- If a report reaches its deliberate high-volume safety ceiling, it now shows as
unavailable rather than presenting a believable but incomplete total. The member directory keeps its existing clearly labelled partial-list mode and disables exports until the complete directory can be loaded.
- Dashboard and household fallback reads, today's attendance totals and member portal-access
history now recover records beyond the first database page. Communications adoption uses exact counts, and Class Pulse labels stay club-bound and complete.
- A failed Class Pulse refresh now removes the cached tile instead of leaving an older score
looking current.
- Once the accompanying database unit is applied, external-event lists, SMS reply badges and the
dashboard attendance chart use live grouped reads instead of transferring histories or issuing repeated count requests. The existing complete reads remain available automatically until then.
- Staff pages with several permission-sensitive panels now resolve one fresh, request-local
permission snapshot instead of re-reading the same club and override settings for every panel. Search, Action Centre and external-event detail responses also expose anonymous server phase timings for performance diagnosis without including club, staff or member identifiers.
- Course audience saves validate all selected programmes in one club-scoped read, pathway lists
batch their entitlement checks, and grading snapshots batch rank ownership and assessor lookups. All three paths still fail closed when an authority read is unavailable.
- Large pathway/course entitlement lists and grading snapshots now page through every source row.
Grading attendance is grouped in the database when the optional read model is available and is otherwise folded exactly one page at a time, removing the former lifetime-history ceiling without keeping the full history in memory. A failed page returns unavailable instead of a plausible but incomplete roster, syllabus, score, attendance or grading history.
- SMS dashboard totals now use a fixed-window keyset walk, so new webhook rows cannot shift pages
while a request is running. Only displayed contacts are name-enriched, recent content is limited to staff with the content-view permission, and segment figures are explicitly labelled as retained-body estimates rather than complete historical billing totals.
- Once the accompanying read-model SQL is applied, the SMS operational dashboard computes its
fixed-window totals inside PostgreSQL and returns only bounded summaries plus 25 recent rows; message history is no longer transferred to the application for aggregation. The weekly attendance read model now also proves the requesting staff member belongs to the session club.
Safer switching and fresher programme lists
- Switching clubs now waits for the server to confirm access before changing
anything in the browser. A rejected or interrupted switch leaves the current club intact; a confirmed switch clears cached member, register, dashboard and API snapshots before a fresh page is shown. Other open tabs follow the same confirmed club and sign-out boundary.
- A browser without a remembered club now chooses the same primary membership
in the page gate and every protected action. The next successful startup restores the secure club cookie automatically, while an unreadable membership never falls back to a potentially different club.
- Staff and member-portal sign-out now reports failures honestly, clears every
local identity cache only at the appropriate authority boundary, and prevents data from the previous account remaining mounted after expiry or a new login.
- Cross-tab sign-out and club switching now purge caches even when a tab is still
bootstrapping or parked on a portal URL. Old in-flight VAT-policy reads cannot repopulate a newly signed-in club's cache, and ordinary non-session 401 errors no longer sign every tab out.
- A confirmed member-portal login now clears household caches in every other
portal tab before reloading it, and page caches are scoped to the confirmed club and household instead of a shared pre-authenticated bucket.
- A temporary reference-data failure no longer replaces known ranks, lessons or
membership plans with believable empty lists. The last confirmed same-club data remains available while the refresh is reported as unavailable.
- Fresh reference data from staff startup now updates an already-mounted screen
immediately through the same club-scoped cache, without polling or another network request.
- Programme member lists are invalidated after confirmed membership changes, so
moving or adding a member is reflected without waiting for the cache timeout.
- Returning to a browser tab now coalesces its focus and visibility signals into
one member-profile refresh, avoiding a duplicate background request while keeping explicit post-save refreshes authoritative.
- After membership is established, independent club-suspension, role and module
authority reads now start together. Every deny and unavailable outcome remains fail-closed; only the avoidable network waiting time has been removed.
- The local performance journey now records each completed route immediately and
retains an explicitly incomplete report if the suite reaches its time limit, so a slow run no longer discards all earlier evidence.
- The remaining browser-native confirmation and alert pop-ups have been replaced
with the accessible AllSorted confirmation and toast surfaces. Consequential actions now state exactly what will happen, rapid duplicate confirmations fail closed, and settings errors no longer freeze the whole browser tab.
- Scenario names and rich-text links now use accessible AllSorted forms instead
of browser prompts. Link editing keeps safe web, email, telephone, page and section links available, with unsafe protocols rejected before insertion.
- Confirmations opened from another modal now use the correct nested layer.
Supported actions can run inside the confirmation, keeping failures visible and retryable instead of closing before the operation result is known.
- Once the second optional live read-model unit is applied, dashboard finance,
programme revenue, Product Sales and Portal Analytics calculate complete live totals inside PostgreSQL instead of transferring invoice, order, session and page-view histories into the application. Browser permissions and club scope are unchanged, integer-pence money remains authoritative, and each caller keeps its complete fallback until the SQL is available.
- Product Sales date ranges now include the entire final UTC day, including
sub-second orders in its last second, instead of stopping at exactly 23:59:59.
- Product Sales now keeps historical orders for a renamed product or variant in
one rollup, identified by its immutable catalogue ID and labelled with its current catalogue name. Deleted catalogue items retain a deterministic order snapshot label, so money and unit totals remain complete without duplicate rows after a rename.
- The optional finance, programme revenue, Product Sales and Portal Analytics
database read models now validate the server-derived staff identity against the selected club as well as accepting only the service role. Their original club-only overloads are removed, direct foreign actors are denied, and every JSON result is bounded before it can reach the application.
- Portal activity analytics now accepts only bounded member-portal pathnames,
and Portal Analytics displays Unknown for a legacy household with no name rather than discarding the complete database aggregate.
Clearer authority failures
- The Action Centre now returns a retryable unavailable response when custom
permission settings cannot be read, rather than silently showing a narrower and believable feed. Capability-gated routes use the same fail-closed rule.
- Existing safeguarding-lead resolution remains intentional: omitted options
resolve the caller's real club setting, while an explicit false value still denies safeguarding-only access.
- Multiple entitlement sources now agree across single-resource and batched
checks: any valid, unexpired grant allows access; a lapsed row cannot mask a current grant.
Deployment note
- The optional
live-query-read-models-01.sqlunit now contains five
actor-bound, service-role-only grouped reads, one locked internal helper and an explicit dependency preflight. External-event entry counts validate the authenticated staff actor as well as event ownership. Apply the unit only through the owner checklist, then require the read-only 6/6 postflight. All application callers retain exact fallbacks, so no feature flag is needed.
- The optional
live-query-read-models-02.sqlunit contains four additional
actor-bound, service-role-only report aggregates. Apply it through the owner checklist and require the read-only 4/4 postflight. Application-first and SQL-first are both safe because all four routes reject malformed results and retain complete fallbacks; no feature flag is required.
- Register setup now arrives in one bounded request, current attendance is
counted in PostgreSQL rather than downloaded to the browser, and realtime bursts coalesce into one final refresh without rebuilding the subscription after each tap. Attendance marking now validates the club, occurrence, lifecycle, lock, programme and package-usage choice in one transaction with deterministic mark-versus-seal ordering; legitimate multi-club staff are accepted through their selected-club access row.
register-performance-authorities-01.sqlis migration-first. Apply it and
require the read-only 7/7 postflight before deploying its three new route callers; no production SQL or feature switch is performed by the application.
- Register removal and sealing now share the same occurrence fence, including
after the final low-level attendance revokes. Live attendance failures show an explicit unavailable state instead of leaving an old count looking current, while an unavailable or oversized optional coach list no longer blocks the lesson, rank and event setup needed to use the register.
- Register package usage again follows the conservative roster rule for any
matching booking history, and malformed recurring lessons with no weekday no longer pass occurrence checks unless they have an exact dated make-up pin. Different-member taps can proceed together, while sealing still waits for all in-flight changes. If the optional coach list cannot refresh, the register now says so without blocking attendance.