2026-07-02
The student profile looks finished at every window size
On wide screens (or zoomed out), the coloured wash at the top of a student's profile used to stop at a fixed width, leaving bare strips either side that made the page look unfinished. The background now runs edge-to-edge at any window size while the profile content stays comfortably centred — and nothing about the mobile layout changed. A sweep confirmed no other screen shares the old pattern.
Cancel many event bookings at once — with or without refunds
Emptying an event used to mean cancelling every booking one by one. The event manage page's Bookings tab now has checkboxes on the booked and attended rows and a Cancel selected button: tick the people to cancel (or select all), confirm, and the whole batch is processed in one go. Confirmation happens in a proper window rather than a browser pop-up: it shows how many bookings are selected, how many are paid and how many are free, and reminds you that waitlist spot offers won't be sent during a bulk cancel. If any of the selected bookings are paid, you choose up front whether refunds go out — the default matches cancelling a single booking (refunds are issued automatically), and cancelling without refunds is always an explicit choice, never an accident: it's a separate option, and you must also tick an "I understand paid bookings will remain paid and no refund will be issued" acknowledgement before the button — which spells out exactly what will happen, e.g. *"Cancel 8 bookings and issue 3 refunds"* — will fire.
Each booking is handled individually and honestly: one that can't be cancelled (or whose refund hits a problem) never blocks the rest, and the result tells you exactly what happened — "7 cancelled (5 refunded) · 1 skipped", with a named message for anyone whose refund or invoice needs a follow-up. Refunds are spaced out behind the scenes so a big paid event cancels reliably, and the same double-refund protection as single cancels applies: retrying a booking can never refund the same payment twice. One deliberate difference from cancelling a single booking: a bulk cancel does not email the waitlist that spots have opened up — when you're clearing an event, those seats aren't really becoming available. The cancelled members themselves still get their normal cancellation messages.
Windows and menus appear reliably on top (first wave)
Dialogs, drawers and menus now sit on one carefully ordered layering system, and the first wave of screens has moved onto it: the main app's student profile, household, settings and key event/calendar dialogs. Most visibly: opening a dialog from a student's profile (editing contact details, changing a plan, confirming a status change, the belt history, and friends) could previously render behind the student name list on the left, leaving it half-covered and seemingly broken. Every one of those dialogs now reliably appears on top, along with the equivalent windows on the households, events and settings screens. On the migrated screens the order is guaranteed: menus float above panels, dialogs above menus, a confirmation opened from a dialog above that dialog, the security timeout above everything except toasts — which always win. Nothing about what the windows *do* has changed; they just show up where you can see them.
Not every screen is on the new system yet: a number of older dialogs — the register screen's overlays, the grading and external-events windows, some communications pop-ups, and the public sign-up and member-portal pages — still use the previous layering. They behave correctly on their own pages today and will be brought across in later waves (the calendar's closure form, the trial-embed window and the voice-note editor came across with this release). An automated check now stops badly layered windows sneaking back into the new system; it polices the new-style layer values, so the older screens stay on a tracked backlog rather than under the check.
A second batch follows in the same release: the new-task dialog, the automations "how would you like to build this?" chooser, the communications template picker and its test-send / confirm-send / invite-to-event windows, the staff trial-booking dialog, the password-confirmation gate used for sensitive undo actions, the add-student wizard's saving screen and duplicate-email warning, and the video-review flag/archive/delete confirmations. As always, nothing about what these windows *do* has changed — same buttons, same behaviour — they simply can never be trapped behind other parts of the page any more. The communications preview and history side panels deliberately stay as slide-over drawers; that's their correct layer, and the automated checks now assert it.
A database hiccup no longer masquerades as "not found"
Across nine more areas — saving a student signature, changing a student's status, student achievements, household details and contacts, communication flow steps, email template versions, lesson drill tags and lesson plan feedback — a temporary database problem during the ownership check used to come back as a flat "not found", exactly as if the record didn't exist. Staff would retry, assume the record was gone, or file a confusing bug. Those checks now tell the truth: a record that genuinely doesn't exist (or belongs to another club) still says "not found", word for word as before, but an infrastructure error now surfaces as a proper server error that's visible in monitoring — so a wobble can't silently pretend your data has vanished. 114 automated tests pin the exact wording and behaviour of every one of these responses.
Two people editing the same thing no longer overwrite each other
If two staff members have the same record open — an automation draft, or an event's details — the second save used to silently wipe out whatever the first person had just changed, with nobody the wiser. The app now notices the record was changed since you opened it and warns instead of overwriting: *"This was changed elsewhere. Refresh to see the latest, then re-apply your edit."* Nothing you typed is lost — your form stays on screen, so you can check the latest version and apply your change again. This protection covers the automation builder's Save draft and event editing from the calendar first, with more screens to follow. (Rescheduling an event that already has bookings takes a separate path that isn't covered yet.) The same protection now also covers three settings screens: editing an event type, editing a class on the timetable, and editing an automation category.
Automations know exactly who they're talking to
Owner and admin Telegram alerts are now resolved strictly per club — your bot token and your admin recipient list, nothing shared, no hidden fallback. And if an automation asks to notify the owner before Telegram is set up (no bot connected, or no admin recipients added under Settings → Club Details → Telegram Integration), the step no longer pretends it succeeded: the run log shows a clear "owner Telegram not configured" skip, and the rest of the automation's actions carry on regardless. The "Member + Admin" copy on a Telegram step now records whether the admin copy actually went out, too.
Emails about a member can now be addressed to the household bill payer — the household's billing address, or its primary contact — which is usually the right inbox for payment reminders about a child. When no payer can be resolved (no household, or no address on file), the message falls back to the member's own contact and the run log records exactly why. Test mode is unchanged and watertight: every address, including a payer's, obeys the same live/production/test-allowlist rules on every path — including reminders that wake from a schedule.
Automations → Upcoming has grown up. Every durable scheduled job — a paused "wait 2 days" step or a timed trial/event reminder — now shows who it's about ("Trial — Sam Smith, 17/07 18:00", "Event — Autumn Camp — Ava Evans"), who will receive it, what it will do (email, SMS, task…), exactly when, and which automation (and version) created it. Jobs whose wake-up is held by the external scheduler carry a small badge so you can see how they'll fire.
It's no longer just the future, either: new filters let you flip between what's still Scheduled, what already Fired, what was Cancelled or Superseded (say, after a reschedule — with the reason shown right on the row), and what Failed or Skipped, plus a trigger-type filter and a due-date range. The default view is unchanged: what's due to go out next, soonest first.
Cancelling or rescheduling now tidies up its reminders
When a trial or event is cancelled, its pending timed reminders are retired on the spot — not left to quietly expire. When one is rescheduled, the old reminders are marked superseded and fresh ones are scheduled against the new start time automatically. And when you publish a reminder automation with a timing, it now also picks up bookings that already exist — so "2 hours before" applies to everyone, not just people who book after you press publish. If a reminder's background job can't be reached to cancel, no matter: the database is the authority, and a stale wake-up always stands down without sending. Any genuine database error at send time now fails loudly (visible in monitoring) rather than being mistaken for "booking gone".
Timed trial and event reminders — "2 hours before start"
Trial and event reminder automations gain a "Send when?" control: keep the default day-before reminder, or choose an exact offset — say, 2 hours before the trial starts. Timed reminders are scheduled the moment the booking is made, anchored to the real start time (not a blind delay), and re-check everything before sending: the booking still stands, the event isn't cancelled, the start time hasn't moved, the automation is still published and the club still has automations on. If any of that changed, the reminder quietly stands down instead of sending something stale. Each automation owns its reminder exactly once — a timed automation is skipped by the old daily sweep, so there's no double-send path.
The builder also now refuses impossible condition combinations at publish (e.g. using a date window on a text field), so imported or hand-edited automations can't sneak past validation.
Scheduled automation waits are now durable
Behind the scenes, every scheduled automation wait (the "wait 2 days then continue" steps) is now always recorded in the database before any external scheduler is involved, so a configuration change mid-wait can no longer lose a pending automation. When a wait wakes up it also re-checks that the club still has automations enabled (and isn't the demo club) before sending anything — a long wait can't outlive a decision to switch automations off. A light daily tidy-up job recovers anything that gets stuck.
Dropdowns and popovers now stay on screen
Small anchored menus — the equipment checklist on a grading event row, the ⋯ actions menu in the automations table, the timetable Export menu — used to open blindly downwards. Near the bottom of the window they could run off-screen or stretch the page with an extra scrollbar, and inside scrolling tables they could be clipped mid-menu.
They now share one smarter popup system: each menu opens above or below its button depending on where there's room, slides in from the button rather than popping from nowhere, never crosses the edge of the screen, scrolls internally when space is tight, and follows its button when you scroll. Escape or a click elsewhere closes them, and keyboard focus lands back on the button you started from. Everything the menus *do* — ticking checklist items, archiving automations, exporting PDFs — is unchanged.
A second wave brings the everyday header and comms dropdowns onto the same system: the top-bar navigation menus, the club switcher, the program lens, the little "i" info popovers on settings headings, and the Test SMS / Test Telegram popovers on the bulk-send page. Same upgrade, same promise: what each menu does hasn't changed, it just can't fall off the screen any more.
A fourth wave covers search-as-you-type and the last everyday menus: the register's add-student search, the task screen's student search (including inside the new-task window), the tasks mobile action and status menus, the student list's bulk status menu, the household "+ Add Member" menu, and the grading rubric's icon picker. Search lists now follow their box, flip upward near the bottom of the screen, support full keyboard navigation (arrow keys, Enter to pick, Escape to close), and announce themselves properly to screen readers.
A third wave finishes the job on the busiest screens: the Sort panel and the Send-options and Add-student menus on the Students page, the Sort panel on the Communications recipient sidebar, the "+ Insert tag" picker in the event editor (which now reliably sits on top of the event window instead of hiding behind it), and the Insert form link / Insert snippet pickers in the email composer. As before, everything these menus *do* — sorting, sending forms, adding students, inserting tags and snippets — is exactly as it was; they just position themselves properly, close on Escape or a click elsewhere, and hand focus back to the button that opened them. A new automated check now stands guard so old-style, clippable dropdowns can't sneak back into the app.
Waitlist promotions get their timed reminders too
Timed event reminders ("2 hours before start") were scheduled when a booking was first made — but a member who reached "booked" from the waitlist slipped past that moment. Now every route to a seat schedules the same reminders: a staff promotion from the waitlist, a member claiming a freed spot on a free event, and a paid claim the instant the payment is confirmed and the seat is actually won. Retries can't create duplicates, and a reminder hiccup can never interfere with the promotion or the payment itself.
The automation builder now speaks plain English
Simple setup has had a polish pass so building an automation reads like a sentence: when this happens → only continue if… → then do this. The conditions section stays out of the way until you actually add one, and everything the builder says about your automation — the plain-English summary, the preview chips, each step's one-liner — now uses friendly wording throughout ("Previous status is Active", never raw internal field names).
The Annual requirement due trigger gets the same treatment the status-change trigger already had: instead of a technical field/operator/value row, you set a simple "Expires within [30] days" window — and the summary reads exactly that, e.g. "expires within 30 days" for an insurance renewal chase. It's stored as an ordinary condition underneath, so anything built before (or anything more unusual) still opens in the familiar condition editor with nothing hidden.
Email steps also gain a Send to choice: the member's own contact (as always), or the household payer — the right inbox for payment and renewal emails about a child. If no payer address is on file, the message safely falls back to the member's contact and the run log says why. And timed reminders now spell out their one honest edge case: someone who books *after* the reminder's send time has already passed won't get that reminder.
Going back from an event returns you to the day you came from
Opening an event from a calendar day and clicking "Calendar" used to drop you back at the calendar root, losing the day you were working through. The event page's breadcrumb now reads Calendar › the event's day › event name, and the new middle link takes you straight back to that day's view. It's worked out from the event itself, so it still points at the right day after a refresh or when following a shared link.
Coach notes, session feedback and incident reports survive bad venue signal too
The same keep-and-retry protection that assessment comments gained now covers two more places staff type things at the venue: coach handover notes on a session's register card, and the session feedback form. If the save can't reach the server, the card shows an amber "waiting to send — will retry when you're back online" note, keeps your text exactly as typed, and sends it the moment the connection returns (or on Retry now). Retries can never save a duplicate or an older version over a newer one.
The two places this mattered most are now covered too: notes typed on a grading day. The day panel's booking notes used to be able to vanish silently if the connection dropped at the wrong moment — the save looked fine but never happened. And an examiner's judging notes could actually be erased from the screen after a second of bad signal. Both now keep your text exactly as typed, show the same amber "waiting to send" state, and send the moment the connection returns — an examiner's note even survives reloading the page mid-outage. Scores themselves behave exactly as before.
The accident/incident form gets a different, equally important safety net: everything you type into the report now autosaves to this device as you go. If the page reloads or the tab closes mid-report — exactly the moment that tends to happen during a real incident — reopening the form offers to restore your draft ("Draft restored from this device"), which you can keep or discard. Submitting still happens in one deliberate step, exactly as before, and clears the draft.
Assessment comments survive bad venue signal
Sports-hall Wi-Fi drops mid-session; typed comments shouldn't drop with it. On the assessment grid, a comment you write with bad signal is now kept and retried — never silently lost. If the save can't reach the server, the comment stays visible with an amber "waiting to send — will retry when you're back online" banner (and inside the technique's comment list), then sends itself the moment your connection returns — or immediately via the Retry now button. It even survives closing or reloading the tab: pending comments are stored on the device until the server confirms them. Nothing is ever marked as saved until the server actually says so, a retry can never create a duplicate comment, and a genuine rejection (say, an expired session) is shown to you straight away rather than retried forever.
Repeating events — and a quicker way to create any event
Events can now repeat properly. When you create an event, a new repeat picker offers options built from the date you chose: every X days, weekly or fortnightly on that weekday, monthly on the same date, or patterns like "monthly on the 3rd Tuesday" and "monthly on the last Friday" — with a chip preview of every date before you save, so there are no surprises. The old monthly repeat could drift at the end of a month (a 31st sliding into early the month after); the new date maths can't. Repeating events are saved as a linked series, and cancelling one now asks the honest question: just this event, or this and all future events in the series? Cancelling the future events follows the same bookings-first rule as always — any occurrence that still has active bookings is skipped, never silently cancelled, and the result tells you exactly what happened ("4 cancelled · 1 skipped — still has active bookings"), listing the dates that need a follow-up. This same choice now appears everywhere an event can be cancelled — the day view and the event's own manage page both offer it for events in a series.
Creating any event got faster too. The type picker is now a single step — every event type on one screen, grouped under its category heading — and if you only have one type, you land straight on the details. The end date stays out of the way behind a Multi-day event toggle (most events are one day; the end date simply follows the start date until you say otherwise). The form also remembers the start time you last used for each event type, and the sign-up cut-off and early-bird deadline gain one-tap presets — "1 day before", "3 days before", "1 week before" — computed from the event's own date and time, with anything that would land in the past sensibly greyed out.
Invoices for paid items now show their real line amounts everywhere
A paid invoice could show £0.00 against its line items — on the PDF a parent downloads from their confirmation email, on the public "pay this invoice" page, and in the household billing view — even though the invoice total at the bottom was right. It affected competition/external-event entries and older event-booking and enrolment invoices, and it was purely a display fault: the money taken and the totals stored were always correct. Every place an invoice renders now computes each line the same way, preferring the exactly-stored amount and otherwise deriving it from quantity × unit price — so a £20 entry fee reads as £20.00, never £0.00. New competition-entry invoices also store their line totals outright so they're right at the source.
Behind the scenes, the invoice screens are more honest when something goes wrong. Previously a database hiccup could quietly render a PDF with no line items at all, show a parent an empty "no invoices" payments page, or turn a legitimate pay link into "Invalid link". Now: the PDF says plainly that line details are temporarily unavailable (with the correct total still shown), the portal and staff billing views show a clear error instead of pretending there's nothing there, and every such failure is logged with the invoice reference so it can be chased. Promoting someone off an event waitlist also now ties the invoice it raises to that booking — so when the family pays, the payment settles that exact invoice rather than ever creating a second one.
Two follow-up polish points from the independent verification of this fix: invoice PDFs downloaded earlier the same day can no longer serve a stale pre-fix copy (the PDF cache now refreshes itself the moment the invoice rendering changes, not just when the data does), and the public pay page now distinguishes a temporary problem — "could not load this invoice right now, try again in a few minutes" — from a genuinely dead link, so a parent with a valid link is never wrongly told it's invalid during a brief outage.