== Changelog ==

Gather Grove Booking: releases older than those listed in readme.txt.

= 1.171.1 =
* Fixed: cancelling a booking that was paid with a pack could fail to give the session back. On packs where the booking had never been counted, the database refused the whole update, so the member's remaining sessions were not credited. Sessions are now always restored.
* Changed: the public schedule and class-grid renderers close their output buffer on the same straight-line path as every other buffer in the plugin; the exception path discards it explicitly.

= 1.170.4 =
* Fixed: payments made for a child showed up in Reports with the Client column blank. A family membership is bought for the child, and a child has no login of their own, so there was no name to print — the money was always counted, it just had nobody's name against it. Rows now read the child's name, with a new "Paid By" column naming the parent, in the transactions list and the CSV export alike.
* Fixed: a parent's own Purchases tab said "No transactions found for this client" even after they had paid, because their children's payments were filed under the children. The tab now shows everything the household has paid, with a Member column saying who each payment was for.

= 1.170.3 =
* Fixed: nobody could create an account from the sign-up pop-up. If you ask new members for a phone number — which is the default — the pop-up never showed a phone box but still refused to continue without one, so anyone signing up from a class page or an RSVP hit "Please enter a phone number" with nowhere to type it. The box is now there. The separate Login and Register pages were never affected.

= 1.170.2 =
* Fixed: two membership and household lookups rebuilt so the WordPress.org security scanner can verify them. No change to what they return.

= 1.170.1 =
* Changed: updated the bundled Gather Grove core to 0.55.0.

= 1.170.0 =
* Fixed: a studio that started using Pulse after it had already been selling could never get its membership history across. Booking has always told Pulse about a purchase as it happens, but it had no way to hand over the ones that came before — so "everyone on the $99 tier" or "when someone buys a membership" simply had nothing to work with, and the import said it had finished. Past purchases, memberships and renewals now come across with the rest of the history.
* Fixed: a renewal now arrives as its own event on the day it was paid, rather than everything folding back onto the day the member first joined. Without that, a member who has paid every month since 2023 looked like they last bought something two years ago — which is exactly the kind of member a win-back campaign should never be mailing.
* Fixed: purchases imported from another platform came across dated to the day the import ran and priced at zero. They now carry the date and price they actually had.
* Added: Booking now tells Pulse which kinds of past activity it cannot hand over, and why, so "the import finished" stops being mistaken for "you have everything". Past failed and recovered payments are the honest gap — Booking records where a payment ended up, not the moment it went wrong, so those can only be captured from now on.

= 1.169.0 =
* Fixed: if you asked for access to a members-only class while booking for someone in your household, the access was granted to *you* instead of to them — so the studio approved it, you were told you were approved, and the class still turned you away. The request now asks who it is for, and the approval lands on that person.
* Added: a "Who is this access for?" choice on the request panel, listing exactly the people you are allowed to book for. Anyone who already has the membership, or already has a request waiting on it, is left off.
* Fixed: emails about a membership request now go to whoever asked for it. A request made for a child or a family member without their own login used to resolve to an address that did not exist, and the approval email quietly went nowhere.
* Fixed: the studio's request email now names the person the membership is for, not just the person who asked — approving puts the membership on their account.
* Fixed: after your request was approved, an adult family member you book for could disappear from the checkout you had just been told to complete.

= 1.168.0 =
* Fixed: a parent could see a family member in the booking form's "Who is this for?" list, choose them, and be told "You don't have permission to book for that person". The list showed everyone in the household; the booking check allowed a narrower set. They now come from the same place, so the list can never again name somebody the booking is going to refuse.
* Fixed: an adult you had granted "Can manage children" got no "Who is this for?" list at all, so the one person that setting exists to help had no way to say which child they were booking. They now see the children they are allowed to book for.
* Added: supported adults. Someone with their own account can now say that the adults in their household may book classes and appointments for them — for a self-directed or day-programme participant, an adult child, or anyone else who has their own login and would rather a parent or caregiver did the booking. Turn it on from the Household section of your account, or ask the studio to set it up.
* Added: studios can record the arrangement from Clients → Household, with a new "Booked for by household" column. The person can always turn it off again themselves.
* Changed: the household roster now says why you cannot book for a linked adult, on the roster where you added them, instead of leaving you to find out at checkout.
* Note: only the person themselves or the studio can turn this on — a payer cannot grant it to themselves over another adult. Memberships remain non-transferable: a supported adult needs their own pass, which a caregiver can buy and use on their behalf.

= 1.167.0 =
* Fixed: booking and course confirmations no longer promise "a full refund" for cancelling. Cancelling has never returned money automatically — refunds have always been the studio's own decision — so the email was committing you to something the software does not do.
* Fixed: the cancellation email no longer tells a member that a named amount "will be returned to your original payment method within 5-10 business days". It now states what they paid and leaves the refund to you.
* Changed: confirmations now show YOUR Cancellation Policy from Settings, the same words shown at checkout. Previously the emails ignored it and stated their own rule, so a studio could advertise one policy and have its confirmation contradict it.
* Note: the "cancellation window" setting has never been enforced anywhere in the booking system, and this release does not start enforcing it. Members can still cancel at any time. Set your Cancellation Policy text to describe what you actually do.

= 1.166.0 =
* Fixed: the studio's address now also appears on appointment confirmations, course enrolment confirmations, the email a guest gets when someone books them a seat, and the note a substitute teacher receives — the four other messages sent to someone expected to turn up somewhere.
* Fixed: "Add to Calendar" carries the address everywhere it appears, including on a member's account page and in an instructor's subscribed calendar feed. A substitute covering an unfamiliar room gets a calendar entry that actually navigates.
* Note: cancellations, rejections and "you are back on this class" notices deliberately do not show an address — nobody is being asked to travel.

= 1.165.0 =
* Fixed: booking confirmations now show the studio's street address under the location name. They named the place but never said where it was, which for a studio whose name is its brand left a first-time student with nothing to navigate to.
* Fixed: "Add to Google Calendar" and the calendar invite attached to a confirmation now carry the full address, so tapping the entry for directions actually finds the place instead of searching for the studio's name.
* Fixed: a suite or unit number entered on a location was never included in any address, sending students to the right building and the wrong door.
* Changed: addresses now read "Crestone, CO 81131" rather than "Crestone, CO, 81131", and are formatted the same way everywhere they appear.

= 1.164.0 =
* Added: promo codes now work on the enrolment checkout link, so an offer made at an info session or agreed with one student can be redeemed by typing the code — the discount is worked out on your server from the code and the package price, never from the link.
* Added: a promo code can be limited to specific packages, so a code written for one programme cannot be spent on anything else.
* Fixed: a code that cannot be used says so and returns you to the page you typed it on, instead of quietly sending you to Stripe at the full price.

= 1.163.1 =
* Fixed: since 1.162.0 the booking window opened from the schedule did nothing on some pages, because the booking script's settings were attached before the script existed. Found in the browser check, not by the tests; a test now pins the order.

= 1.163.0 =
* Changed: housekeeping found by review, none of it visible. Four empty placeholder files, twenty-nine methods nobody called, five unused constants, eight unused imports and about forty-five stylesheet rules that matched nothing are gone; three screens share one status-badge helper and three exports share one CSV writer; every dollar amount goes through the currency formatter (a studio charging in another currency saw a stray "$" in three places); the plugin's scripts keep their helpers under one namespace; and the developer notes stop describing widgets and list tables that were removed months ago.

= 1.162.0 =
* Changed: the public schedule, class lists, calendar and instructor dashboard now read each class's details once per page instead of several times per row, so busy schedules load with a handful of database reads.
* Changed: the Front Desk roster, Waiver Compliance report, Invoices list, Bookings list, Clients list and CSV export, package shop and enrolment picker each make one read for the whole page instead of one per row.
* Changed: attendance now has database indexes for the counts and reports that read it, so milestone and attendance lookups stop scanning the whole table as history grows; the mail log is indexed for the reminder sweep.
* Changed: generating a season of sessions clears the schedule cache once, not once per session; scheduling an automation is a single write that cannot double-schedule under load; email styling and custom class labels are resolved once per batch of emails.
* Changed: Booking's stylesheet and scripts load only on pages that actually contain a Booking widget.

= 1.161.0 =
* Fixed: typing a member's email address into the guest checkout — for a class, an appointment or a gift card — attached the booking to that member's account, emailed them a confirmation they never made, and quietly turned their account into a "guest" one. An address that belongs to a member is now asked to log in; a returning guest can still book without a password.
* Fixed: "book for someone else" could be used without logging in, minting accounts for any email address and booking them past the waiver. It now requires a logged-in buyer, and the option only appears when you are logged in.
* Fixed: the "you already have credit that covers this class" prompt listed a member's packs and balances to anyone who typed their email. It is only offered to logged-in members now.
* Fixed: a family promo code took the household size from the form, so a single-person household could claim the large-family discount on twenty bookings from one code. The size is now the household on record.
* Changed: checking a promo code while logged out gives the same answer whether the code is new-clients-only or already used by that address, so an address cannot be probed. Household invitations and gift-card redemption attempts are rate-limited.

= 1.160.0 =
🚨 Fixed: a declined card on a class that requires approval held the seat forever — nothing could cancel or sweep a pending-approval booking, and the retry took a second seat. It is released like any other failed payment now.
🚨 Fixed: a class could be booked on an expired, used-up or wrong-class pack — only ownership of the pack was checked. Every condition is checked now, and a pack that fails to debit no longer leaves a "paid" seat behind.
🚨 Fixed: rejecting a booking never returned the member's pack session, account credit or promo code, never offered the seat to the waitlist, and refunded the card before the seat was released. All four are right now.
🚨 Fixed: a third of classes never got their day-before reminder — the reminder looked 20 to 28 hours ahead but only ran twice a day, so anything in the gap fell through. The window now covers the whole period.
🚨 Fixed: birthday greetings never went to anyone who typed their own birthday into their profile; anniversary and birthday emails could go out a day early or late in your timezone; and 1:1 appointments were flagged "under-enrolled" every day.
* Fixed: booking extra seats from a waitlist offer skipped the capacity check; cancelling a group booking offered only one of its seats to the waitlist; a waitlist offer accepted in the same minute it expired could be marked expired and lose its seat; undoing a late cancel revived a guest seat that had been cancelled separately; retrying a declined card handed your seat to the waitlist before the retry landed; a double-click could cancel the booking still being paid for.
* Fixed: recording the same walk-in twice created a second seat and a second invoice; paying an invoice for a cancelled seat sent a "you're booked" email; a package paid by invoice never got its record or its purchase hooks, and the second one on a site failed silently.
* Fixed: cancelling a course sent one "booking cancelled" email per remaining week (with a session number as the reason), never offered the freed seats to the waitlist, and double-counted a session you had already late-cancelled. It sends one email now, listing the released dates and the refund.
* Fixed: ending a membership immediately released one seat per booking instead of every seat, and skipped the normal cancellation (waitlist, invoice); it also now sends the member one membership-ended notice instead of a flood of per-class ones.
* Fixed: an expired class pack blocked buying the same pack again; "sessions remaining this period" on a membership always showed the full allowance; a $0 membership granted on approval became unlimited forever; a payment plan that recovered on its next installment never told Pulse, so dunning emails kept going; a deposit plan wrapped after its trial billed one installment short.
* Fixed: turning an automation on for one class did nothing when it was off studio-wide; "getting ready" and "your class changed" emails still went out for a cancelled class; moving a class did not move its queued roster and prep emails; two overlapping cron runs could send the same automation email twice; the Review Request "trigger after (hours)" setting was ignored; "renews soon" and "expires soon" emails still went out when the date had moved.
* Fixed: the Front Desk "Add Client" walk-in modal always showed a $0.00 drop-in price.

= 1.159.0 =
* Changed: every Gather Grove Booking admin screen now uses the same cards, status badges, banners and empty states that Pulse's admin does, so the two read as one product side by side. Lists sit in cards with purpose-built rows instead of WordPress list tables; statuses are colour-coded badges everywhere; a screen with nothing on it yet says what to do first. Filters, search, sorting, paging, bulk actions and every row action work as before.
* Changed: the shared component stylesheets now load from the vendored core as registered handles instead of hand-copied files — Booking's copy of the admin chrome sheet had drifted behind core's and nothing said so. A test now fails if a component core ships is not loaded, if any screen renders outside the shared helper, or if a helper goes unused.
* Fixed: cancelling a booking from the Bookings list painted the status pill with hard-coded colours instead of the studio's badge style; marking a session complete could relabel the wrong pill in a row that also showed "Customized" or "Sub"; the Bookings list showed a plain grey pill for waitlisted, attended, late-cancel and scheduled rows; the Update Attendance button on a booking's detail page never refreshed the pill after saving; the Membership Requests Approve/Reject buttons updated the wrong cells; the client Documents tab could not show its empty state after the last file was deleted.

= 1.158.4 =
* Changed: on a site with an Elementor palette and an empty Brand tab, the Gather Grove admin screens now take their accent from your site's own palette instead of Gather Grove green, matching what the front end already did. Vendored core 0.53.0.

= 1.158.3 =
* Fixed: the admin Sessions and Bookings lists showed "No items found" for every filter on MySQL 8, and the class-participants email export returned nobody. 1.157.0 guarded each optional date bound with ( %s = '' OR column >= %s ); MySQL 8 refuses to compare a DATETIME column with '' even when the flag in front of it is true, so the whole statement failed. Unset bounds now bind the edges of the DATETIME range and compare directly.

= 1.158.2 =
* Fixed: notification emails were hard to read on a phone. In a roster or reminder, the label column ("Class", "When", "Location") sat beside its value and squeezed it to about a third of the screen, so a two-word class name wrapped onto three lines. On a phone the label now sits above its value, each using the full width. Nothing changes on a computer. Vendored core 0.48.2.

= 1.158.1 =
🚨 Fixed: a logo with a transparent background could arrive on a black rectangle. 1.158.0 made an email-safe copy of any picture your site had converted to WebP, but always made it a JPEG — and JPEG cannot store transparency. Transparent images now stay PNG. Already-converted images are rebuilt automatically.

* Fixed: emails were too narrow to read on a phone, and now tighten their spacing on small screens.
* Changed: emails declare that their design is finished, so mail apps that respect it stop repainting them for dark mode.

Vendored core 0.48.1.

= 1.158.0 =
🚨 Fixed: your logo now shows up correctly in Outlook. Two separate problems in the same place, both invisible from inside the site because the only people who saw them were the recipients of your emails.

First, the size. The logo carried only CSS sizing, and classic Outlook ignores it — so a square logo that looked right everywhere else rendered at its full uploaded size in Outlook, pushing the rest of the header out of shape on every booking confirmation, reminder and cancellation. The logo now states real pixel dimensions, which is the only sizing Outlook reads.

Second, the format. If your site converts images to WebP for speed — many do, often through a plugin or your host — the logo we picked up was a WebP, and classic Outlook cannot display a WebP at all. Those recipients saw a broken-image box where your logo should be. Gather Grove now makes a JPEG copy for email automatically, once, and uses that.

Nothing to configure, and nothing changes on your website. Vendored core 0.48.0.

= 1.157.0 =
* Security: a client or instructor account can now be removed from Gather Grove screens only when every role on that account belongs to Gather Grove. Accounts that also hold a WordPress role (editor, author, subscriber, …) are left in place; the WordPress Users screen remains the only place to delete those.
* Security: the client-profile editor no longer renames WordPress accounts that Gather Grove does not manage; it updates the Gather Grove client record only.
* Changed: every database query is now a single fixed statement with every value bound through `$wpdb->prepare()`; list filters use bound flags rather than assembled `WHERE` fragments.
* Maintenance: updated the bundled Stripe PHP library from 20.3.1 to 21.3.0.

= 1.156.0 =
🚨 Fixed: the Gather Grove admin screens were unreadable on a dark studio brand.
Our admin CSS reads the shared --gg-color-* tokens, and on an admin screen those
were already set to the studio's FRONT-END palette, so a dark brand painted cream
text on WordPress's light admin chrome — the dashboard heading measured 1.01:1,
effectively invisible, alongside eight further contrast failures on that one
screen. The admin now emits core 0.47.2's contrast-guarded admin palette, which
re-points those tokens at WordPress's own admin colours and clamps the studio's
brand hues so they stay legible. Light-brand studios are unaffected. Vendored
core 0.47.2.

= 1.155.0 =
* Fixed: Gather Grove Admins can now actually save Settings. The Settings page was gated on the "manage settings" capability, but WordPress required full site-administrator rights to save it — so a Gather Grove Admin could open Settings, edit any field, press Save, and be told they were not allowed to manage options for this site. The capability now means what the menu always claimed it meant.
* Changed: the Payments tab is now visible only to full site administrators. It holds live Stripe credentials, which stays owner-only — matching Factory Reset and the Stripe connect button. Nothing a Gather Grove Admin can see is now unsaveable.

= 1.154.1 =
* Fixed: the booking form no longer offers "Buy a Pack" or "Subscribe" on sites that have not connected a payment gateway yet — those purchases could be selected but never completed. Drop-in booking and existing credits are unaffected.
* Fixed: packages on the starter "Packages & Memberships" page now lay out in a proper grid instead of a single narrow column.
* Fixed: the starter page importer no longer stops part-way when another Elementor template on the site has corrupt display-condition data. It reports what it could not finish, and running the import again now repairs any templates left without display conditions.

= 1.154.0 =
* Changed: every session activity event (booked, attended, no-show, cancelled) now carries the class it was about — class id, class type, its category (and all categories) and the start time — so Gather Grove Pulse automations can act on *what kind* of class someone booked, not just that they booked one.

= 1.153.4 =
* Fixed: booking an appointment with an incomplete card no longer leaves a hidden hold on the slot. The form now asks for complete card details before creating the booking, releases the slot immediately if the card is declined, and — when a booker tries again after a failed card — takes their own slot back instead of silently placing them on the waitlist.

= 1.153.2 =
* Changed: bundled Gather Grove Core updated to 0.46.1 (documentation-only release).

= 1.153.1 =
* Changed: bundled Gather Grove Core updated to 0.46.1 (documentation-only release).

= 1.153.0 =
* Added: a "Start Zoom Meeting" host button on the instructor dashboard and a "Start meeting" link (plus a Start Zoom Meeting action) on the admin Sessions page for sessions whose Zoom meeting Booking created. The click fetches a fresh host link from Zoom, so it always works — and falls back to Zoom's sign-in-to-start page if Zoom can't be reached.
* Added: the attendee join link is now shown to instructors on the dashboard and to managers on the Sessions page for every virtual session.

= 1.152.0 =
* Added: Find a Sub now emails — other instructors get the request, the manager and requester hear when someone claims it, and the sub gets a confirmation.

= 1.151.3 =
* Changed: the remaining dynamically assembled queries (booking and session lookups, the instructor dashboard and digest, the report bucketing, notification read-marks) are single fixed statements with every value on a placeholder.
* Changed: integer values printed into booking-form and package-shop markup are cast explicitly; the page pickers in settings escape their arguments.

= 1.151.2 =
* Changed: every database query names its tables with WordPress's identifier placeholder (%i) through $wpdb->prepare(); no table name is concatenated into SQL any more.
* Changed: request handlers that read form fields verify their nonce in the same function as the reads, even where a caller already did.

= 1.151.1 =
* Security: the pre-booking intake metabox fields verify their own nonce and edit capability before reading anything from the request.
* Changed: the Reports page's CSV export, date-range and tab scripts moved into the admin script bundle — no script is buffered inline any more.
* Changed: every output buffer opens and closes on one straight-line path; the exception path discards the buffer explicitly.
* Changed: the pending-automation cancel query and the prefix-rename scan are single fixed statements with every value on a placeholder.
* Removed: three files nothing loaded (an empty Classes admin-page stub, an empty cancellation-policy stub, and unreleased reports location-switcher scaffolding).

= 1.151.0 =
* Added: buy a session bundle while booking. On a service's own booking page the payment step can now offer "Buy a session bundle (<pack>)" for any published Service Pack that covers the service and that the client doesn't already hold. Pick how many sessions (on a variable pack), see the live total, enter a card once: the bundle is charged, created for you (guests get the same account a single paid appointment creates), the appointment is booked from it and the rest are saved for later. Studios can switch the offer off per widget ("Offer Session Bundles").

= 1.150.0 =
* Added: let clients choose how many sessions to buy. On a Service Pack, tick "Let buyers choose how many sessions" and set a min/max; the price and session count then apply per session. The shop shows "$111 per session", a −/+ picker and a live total, the charge is the unit price times the quantity (promo codes still apply to that total), and the pack lands with that many sessions to redeem one appointment at a time. Receipts and the client dashboard show the quantity and unit price; staff can comp a chosen quantity from Manual Booking. Fixed bundles like "10 for $999" are unchanged.

= 1.149.0 =
* Added: promo codes for 1:1 appointments. Codes can now apply to appointments (a new "Appointments" choice in Promo Codes → Applies To, or "All"), the appointment booking form gets a promo field with a live total, deposits are worked out from the discounted price, a 100% code books the appointment free, and a code is handed back if the booking is cancelled before it was paid or is refunded.
= 1.148.0 =
* Added: Service Packs — sell a bundle of appointment sessions (for example "10 sessions for $999, valid one year"). Clients who own one see "Use <pack> (N of M remaining)" as the default payment option when booking that service; a session is taken off the pack on booking and returned if they cancel in time.
* Added: a new "Service Pack (appointments)" package type with a picker for the services it covers; session packs appear in the Package Shop and on the client dashboard alongside class packs, and can never be spent on a class (nor a class pack on an appointment).

= 1.147.4 =
* Fixed: a paid appointment is no longer marked as paid (and confirmed by email) before the card is actually charged; it now waits for the payment to settle, exactly like class bookings.

= 1.147.3 =
* Fixed: a visitor booking an appointment without an account on a service that requires a waiver is now shown the waiver to sign and then booked, instead of being told to sign a waiver that never appeared.

= 1.147.2 =
* Fixed: primary buttons no longer disappear on hover and the account dropdown is no longer transparent — Booking now loads its design-tokens stylesheet (and carries fallbacks for every token it uses).
* Fixed: outlined secondary buttons now sit on a solid surface so they stay visible on tinted page backgrounds.

= 1.147.1 =
* Fixed: a package sold by payment plan can now also be paid in full through the hosted checkout link (Klarna and other dashboard-enabled methods appear there when Stripe offers them).

= 1.147.0 =
* Added: subscribe-and-book — pick a membership as the payment option inside the booking form and come out with a live subscription and the seat in one step (self-only, card charged now; a booking failure after the subscription is live keeps the membership).
* Added: "Monthly on a day of the week" schedule frequency — "3rd Tuesday of every month", "last Friday of every month". Existing Monthly and Weekly rules are unchanged.
* Changed: the package Course Access section picks courses by name instead of a raw Course IDs field (falls back to the text field when Courses is not installed).
* Fixed: an owned class pack targeted to one class could be spent on any class at booking time (the redemption check now uses the same targeting as the booking form and shop).

= 1.146.1 =
* Changed: updated the shared Gather Grove core to 0.46.0. Inherits two security fixes: a visitor behind a trusted proxy can no longer spoof the address rate limits are keyed on, and a browser push subscription may only point at a real browser push service. Also fixes the app-shell "ready" signal that never fired. No booking behaviour changes.

= 1.146.0 =
* Security: **a security audit of the booking and payment rails — 26 findings fixed.** The gift-card redeem link no longer accepts a numeric card number (only the unguessable link in the email works) and never prints the card code into the page. A gift booking can only be paid from the buyer's own pack or balance — never the recipient's. Guest checkout no longer lets anyone act as an existing account by typing its email: an email that already has an account is asked to log in. A guest-allowed appointment can no longer be paid from another account's studio credit. Instructors can only take attendance for sessions they teach. Linking an adult to your household now sends them an invitation instead of attaching them silently. The Stripe secret keys are no longer written into the settings page. CSV exports neutralise spreadsheet formulas. `/packages/available` no longer reveals another account's passes.
* Fixed: **a 3D Secure (EU/UK) card buying a class pack was charged but never received the pack.** Both the pack purchase and the buy-and-book flow now complete after the bank challenge. A split (balance + card) booking's card confirmation is verified before it is trusted.
* Fixed: **a pending booking swept by the 15-minute cleanup could still be charged, then "confirmed" on a cancelled seat.** The sweep now checks with Stripe first — a paid intent is settled, an unpaid one is cancelled at Stripe before the seat is released — and a payment arriving for a cancelled booking is refunded, not confirmed. A late "payment failed" event can no longer mark a paid booking unpaid.
* Fixed: partial refunds issued from the Stripe Dashboard now show as partly refunded, with one ledger row per refund; a failed webhook signature is reported back to Stripe so it retries.
* Fixed: cancelling a pack-funded group booking returns every seat it used (a four-seat booking returned none on self-cancel and one on admin refund); refunding a balance-paid booking returns the credit to the balance; a refund that would move no money is refused instead of emailed.
* Fixed: a single-use promo code could be redeemed by several buyers at once; a sliding-scale membership's promo discounted the headline price instead of the chosen rate; invoice numbers could collide under load; settled deposit balances were missing from revenue reports; attended bookings imported by Migrate lost their attendance record; two scheduled jobs kept running after deactivation.
* Added: settings that could be read but never saved now have fields — Google Review URL, Schedule / Memberships / Booking page pickers, in-app reminder lead time, and the outgoing mail rate.

= 1.145.0 =
* Fixed: **an approved family could be quoted the suggested price instead of the rate you agreed with them.** On a sliding-scale membership, once you approved someone's request the payment form filled itself in with the suggested amount and charged that — overriding the figure you had settled on, and setting it as their ongoing monthly rate. The form now starts blank for anyone with an agreed rate, as intended, and bills what you agreed.
* Added: **an "Agreed" column on the Membership Requests screen.** You could enter an agreed amount when approving, but nothing ever showed it back to you. Both tables now display it, and an amount you never set looks different from one you deliberately set to $0.

= 1.144.0 =
* Fixed: **enrolling in a course sent one email per session.** A four-week course sent the student four booking confirmations on top of their receipt, and the instructor four "new booking" notices seconds apart — an eight-week course sent eight of each. A course purchase is one event, so it now sends one email each: the student gets a single confirmation listing every date, time, room and instructor, and the instructor gets a single notice naming the student and all the dates they are now on the roster for.
* Added: **one calendar invite for a whole course.** The enrollment confirmation carries a single "Add all dates to your calendar" file covering every session, alongside per-date links for anyone who only wants some of them.
* Added: two new customizable email templates under Settings → Email → Templates — "Course Enrollment Confirmed" and "Instructor Course Enrollment Notification" — both previewable.

= 1.143.13 =
* Fixed: **the "Date Block (List)" alignment did nothing** on the Class Schedule and Sessions List. The date and time sit in a centred stack, so setting Left or Right simply had no effect. Both now move as you would expect.

= 1.143.12 =
* Fixed: **the date-range picker never appeared while editing.** If you set the date filter to "Date picker", the preview still drew the quick-range buttons instead — so the four colours you can set for the picker had nothing to show them on, and the preview showed a control the published page would not.
* Fixed: **the sessions list could not be styled empty or full.** There is now a "No sessions" preview state for the no-sessions message, and one sample session is full, so the Full Button Text and its colours can finally be seen.
* Fixed: **"Late cancel" attendance colour could not be previewed** — the sample roster only ever showed attended, no-show and pending.

= 1.143.11 =
* Fixed: **the "Enrollment course" editor preview left out the instructor, location, room, capacity and duration.** A real course card shows all five, so those five switches did nothing while you were styling that preview — and anything you set for them had nothing to appear on. The preview now matches the live card.
* Fixed: **the card and list previews showed a class description the published page never prints.** Descriptions appear only in the calendar popover, so the preview was promising a line that disappeared on publish. The popover is unchanged.

= 1.143.10 =
* Fixed: **"Show Filter Bar" did nothing on Card and List views when the editor preview was set to Real data.** The filter bar simply was not drawn in that mode, so the switch — and all the individual filter toggles under it — appeared broken while editing. The live page was always correct.

= 1.143.9 =
* Fixed: **the Instructor and Location styling on schedule cards did nothing while you were editing.** Colours and fonts you set for them never showed on the canvas, because the preview built those lines differently from the real page. The live page was always correct — now the preview matches it.

= 1.143.8 =
* Fixed: **"Show Household Selector" did nothing while editing the Appointment widget.** The "Who is this for?" picker appeared on the live page but never in the preview, so you could not see or style it. It now shows in the booking form when Display is set to Inline.

= 1.143.7 =
* Fixed: **two switches did nothing while you were editing.** "Show Age Range" on a Class Card and "Link Name to Class Page" on a Discovery result both worked on the live page but never changed the preview — so you could not see or style them. Both now preview correctly.

= 1.143.6 =
* Fixed: **four schedule switches appeared to do nothing while you were editing.** Show Description, Show Course Badge, Show Session Count and Show Past Sessions worked on the calendar preview but not on the card or list views — and card is the default, so most people saw no effect at all. All four now preview properly, and the sample schedule includes a course and a finished class so you can see and style those states.

= 1.143.5 =
* Fixed: **the class schedule's search box did not appear while you were editing.** You could style it, but never see it on the canvas. It now previews in every view, using whatever Search Placeholder text you set.

= 1.143.4 =
* Fixed: **the "Show Cancelled" switch on the Instructor Dashboard did nothing while you were editing.** It works on the live page, but the editor preview had no cancelled class to show — so you could not see or style how a cancelled class looks. The preview now includes one when the switch is on.

= 1.143.3 =
* Added: **you can now hide the "Cancel Class" button.** If you would rather instructors did not cancel their own classes and left it with the front desk, switch it off on the Instructor Dashboard. It stays on unless you turn it off, so nothing changes for you by updating.

= 1.143.2 =
* Fixed: **the "Find a Sub" button did not show up while editing the Instructor Dashboard.** It worked on the live page, but the editor preview never drew it — so turning the switch on appeared to do nothing. It now previews properly, including the "sub requested" state, so you can style both before anyone sees them.
* Fixed: a class already marked complete could still offer to find a sub.

= 1.143.1 =
* Fixed: **the new sub-request feature had no database table behind it.** Version 1.143.0 added the feature but only created its table on a fresh install, so sites that updated would have hit an error the first time an instructor asked for a sub. Updating now creates it. Nothing was lost — the feature ships switched off, so it could not be reached.

= 1.143.0 =
* Added: **teachers can find their own sub.** A "Find a Sub" button on each of their upcoming classes asks the whole teaching team at once — everyone gets a notification and an email, and the first person to accept becomes the substitute for that class. Your manager is told when cover is asked for and again when someone takes it, so nobody has to be the middleman. If a class is still uncovered it chases the team again as the date gets closer, and flags it to an admin rather than cancelling anything on its own. Turn it on with the new "Show Find a Sub Button" switch on the Instructor Dashboard.
* Added: **a "Substituted only" filter on the Sessions screen.** Tick it with a date range to see every class that was covered by someone else — who was scheduled, who actually taught it, and when. If you handle teacher pay outside the software, this is the list you need.
* Added: **optional staff-room posts for sub requests.** If you use Community Spaces, point the new "Sub Requests — Community Space" setting at your staff room and each request is posted there as well, giving the team a scrollable record alongside the email and notification.

= 1.142.1 =
* Fixed: **the new "sessions left" count on the roster was too generous for anyone who books a guest.** A member who brings someone uses two classes off their pack in a single booking, but the roster counted the booking rather than the seats — so it showed more left than the person really had. It now counts seats, and a booking that took more than one says so.

= 1.142.0 =
* Added: **your instructors can see how each person on the roster is paying for the class.** A new switch on the Instructor Dashboard shows a small label under each name — Pack, Membership, Credit, Paid or Unpaid — so whoever is teaching can tell a member using a class pack from someone who walked in and still owes. On a class pack it also counts down, "Pack 3/8", showing how many sessions that person had left after that class. It is off until you turn it on, since it shows a client's balance to whoever is teaching.
* Fixed: **cancelling a booking left its invoice payable.** The seat was released but the invoice stayed live, so the client could still be charged for a class they were no longer booked into — and nothing flagged it. Cancelling now cancels the invoice and switches off its payment link. Marking someone a *late* cancel still leaves the invoice payable, which is the point of the distinction.
* Fixed: **paid invoices could be paid a second time.** Settling an invoice did not switch off its payment link, so the link sitting in the customer's inbox stayed chargeable and a second click would take a second payment. Paying an invoice now retires its link, the same as voiding or expiring one already did.

= 1.141.0 =
* Fixed: **one-to-one appointments and private classes could appear in your community's "Next:" strip.** An appointment slot is not something the studio is gathering for, and a private class is private — but the strip showed whichever session was simply soonest, so a discovery call could be announced to everybody as "1 spot left". The strip now shows public class sessions only.
* Added: **the strip shows a member their own next booking first.** If you have something coming up it says so — including a private class or an appointment you booked yourself, because that is yours to see. With nothing booked, you get the studio's next class as before.
* Fixed: **tapping the strip could take you to an unrelated page.** A session with no class behind it linked to whatever page the strip happened to be sitting on. It now links to the offering, or stays put.

= 1.140.0 =
* Fixed: **real membership and package sales were recorded as sample data.** If you run the sample studio and sell something genuine alongside it, the sale was charged correctly but filed as practice money — so it never appeared in your reports, and the nightly cleanup could remove the only record of it. Sales are now judged by what was sold: anything marked as taking real bookings counts as real income.

= 1.139.0 =
* Fixed: **gift cards and course enrolments could still take a real payment while the sample studio was on.** Class bookings, appointments, packages and memberships were all already free in the sandbox; these two were never included. A visitor trying out your demo could have been charged for a gift card, or for a sample course. Both are free in the sandbox now. Anything you have marked as taking real bookings is unaffected.

= 1.138.0 =
* Added: **you choose what happens when someone without an account books something that needs a waiver.** Either they sign before booking (the default), or they can book and are asked when they arrive — in which case "Owes a waiver" now shows beside their name on the check-in list, where somebody is standing in front of them and can collect it.
* Fixed: **a guest could be told to sign a waiver they were never shown.** Booking without an account still creates a record for you behind the scenes, and it is created before the waiver check runs — so the booking form treated the person as a guest and showed them nothing, while the booking engine treated them as a member and asked for a signature.

= 1.137.0 =
* Added: other Gather Grove plugins can now ask who is in a member's household — the family you already set up in Booking, rather than a second list kept somewhere else. Gather Grove Pulse uses it to show a member's family on their record.

= 1.136.0 =
* Added: **Gather Grove now knows when a failed payment has been put right.** Booking already told members and staff when a card went through after a failure, but it never announced it to the rest of your Gather Grove plugins — so anything acting on "their payment failed" had no way to hear that it had since succeeded. That gap is why a member who fixed their card on Tuesday could still be chased about it on Thursday. Recoveries are now announced the same way failures always were, for both memberships and payment plans.

= 1.135.0 =
* Fixed: **an appointment could show up on your members' "next up" list wearing the name of whatever page they were looking at.** Appointments are not classes, and the calendar was looking up the wrong thing to name them — so instead of the service, it printed the title of the surrounding page. On gathergrove.io a real Discovery Call was announced to members as a blog post, with "1 spot left" beside it. Appointments are now named after the service they are, and anything that cannot be named is left blank rather than borrowing a name.

= 1.134.0 =
* Fixed: **your packages and memberships bunched up against the left edge instead of sitting centred.** If you offer fewer packages than fit across the row, the shop was reserving space for the ones that weren't there — so two plans sat squashed to one side with a gap beside them, and no alignment setting in the page builder could move them. They now sit centred whatever the number, and cards no longer stretch oddly wide when there are only one or two.

= 1.133.0 =
* Fixed: **there was no way to say "charge real money for this one" on a class or a membership.** The setting existed for services only, so on a site running the sample studio a genuine class or membership could not be marked as real from anywhere in wp-admin — it had to be done in the database. Since 1.132.0 also made the sample studio comp memberships, a real membership created on such a site would have been given away free. Classes and packages now have the same **Real Bookings** / **Real Purchases** checkbox services have always had.

= 1.132.0 =
* Fixed: **while the sample studio was switched on, buying a sample class pack or membership could still charge a real card.** Sample class bookings and appointments were already free, so it looked like nothing in the sandbox could take money — but packages and memberships were never included, and they went through your real payment settings. They are free now like everything else in the sandbox. Anything you have marked as taking real bookings keeps charging exactly as before.
* Added: **you can now let your real members' email through while the sample studio is on.** Gather Grove withholds any email it cannot tie to a specific booking — password setup, membership warnings, cancelled-session notices — so a sandbox cannot mail your seeded sample addresses. If you have already made those addresses unreachable another way, the new `gather_grove/booking/demo/block_mail` filter lets you release real people's mail without switching the sample studio off.

= 1.131.1 =
* Housekeeping: updates the shared Gather Grove code bundled with Booking. Nothing in Booking changes. Includes a fix that stops background jobs from quietly running more often than intended.

= 1.131.0 =
* Added: **other Gather Grove plugins can now read your past attendance and bookings, not just new ones.** A plugin installed today can be shown what happened last year. Nothing in Booking changes — no new screens, no new settings, and nothing is sent anywhere. Demo and guest bookings stay excluded.

= 1.130.0 =
* Fixed: **if you renamed "Class" or "Appointment", the new word did not reach your email subject lines.** It reached the email body, but a member's inbox still said "Class Cancelled". Subjects now follow your wording like everything else does.

= 1.130.0 =
* Fixed: **your community's "next class" line could advertise a sample class to real members.** If you have seeded the sample studio and not yet removed it, the next thing on the calendar is usually a showroom class — and that is what members saw on their own profile. Sample sessions are now left out everywhere a real member is looking. The sample studio's own pages are unaffected.
* The "next class" line is now a link to that class. It named a class and went nowhere before.

= 1.130.0 =
* Fixed: **the Gather Grove Booking widgets were missing from the Elementor widget list.** All fifteen — Class Schedule, Booking Form, Appointment Booking, Client Dashboard and the rest — existed and worked, but they had no section of their own in the panel, so the only way to reach one was to type its name into the widget search. They now appear together under **Gather Grove Booking**. Pages you have already built are unaffected.
* Fixed: the Class Discovery widget could fail to render when a page was generated outside a normal browser visit — a scheduled task or a command-line cache warm-up, for example. It now degrades instead of stopping.

= 1.127.4 =
* Housekeeping: **bundles Gather Grove Core 0.41.0.** Brings the shared code behind the app-style member experience (add-to-home-screen and class reminders on a phone) up to date, so this plugin is never the one holding a site on an older version. Nothing changes on your site.

= 1.127.3 =
* Fixed: a payment plan could keep charging a student after the plan was paid off — an eleventh payment on a ten-payment plan, with nothing on your side to show it. Stripe retired the setting Booking used to cap the plan and began refusing it, and the refusal was only logged. Booking now uses Stripe's current wording. **Check plans you sold before this release:** in Stripe they appear as a subscription with no end date, so set the schedule to cancel after the final installment, or cancel it once the last payment lands.
* Fixed: if you teach online through a single standing Zoom room set up as a location, every session was published to Google as an in-person event at a venue called "Online — Live via Zoom" with no address — a contradiction Google flags. Booking now reads the "This is a virtual location" tick box, so those sessions are published as online events. Nothing changes for an in-person venue.
* Fixed: a session's meeting link was written into the class page's structured data, where search engines and anything else fetching the page could read it. Search engines are now given the class page instead. Your join link still reaches the people it should — confirmation and reminder emails and the member's dashboard are unchanged.

= 1.127.2 =
* Fixed: enrolling in a course dead-ended for anyone who had not yet signed your waiver — the form vanished and said "Network error", with no waiver shown and no payment taken. The signing step now appears in the enrolment pop-up as it does everywhere else, and the purchase finishes on its own once signed.
* Fixed: on a course that also sells single sessions, the "Just want one session? Drop in for $X" link sent the buyer to a page of raw code instead of the drop-in booking form — but only from the booking pop-up, which is what the calendar and class cards open. On an iPhone it offered to download a file. The "Enrol in the full course instead" link back had the same fault. Both now go to your booking page for that session.

= 1.127.1 =
* Fixed: on some Stripe accounts, checkout failed for every purchase — packages, courses and payment plans alike — sending buyers to an error page. Stripe had switched those accounts to its "Managed Payments" service, which only covers fully automated digital products and rejects sales like live, in-person classes and trainings. Booking now tells Stripe not to use Managed Payments, so checkout works no matter how your account is set. Nothing to change on your side.

= 1.127.0 =
* Groundwork for messaging classmates: Booking can now tell Gather Grove Communities when two people attended the same class. Needs Gather Grove Communities 1.49.0, which is what uses it.

= 1.126.1 =
* Fixed: the "Book" and "Book Now" buttons turned a fixed dark green on hover whatever palette you had chosen. They now darken your own colour instead.

= 1.126.0 =
* "Your class starts soon" now appears in a member's notification bell — and on their phone, for anyone who has added your studio to their home screen.
* Set how far ahead in Settings. An hour is the default; 0 turns it off. It is separate from the reminder email, so you can send an email the night before and a nudge an hour ahead.

= 1.125.0 =
* Groundwork for phone notifications: the bell now says when it has recorded something new, so the app tier can decide whether it is worth waking a member's phone. Nothing changes on its own, and most notifications will never be pushed.

= 1.124.0 =
* Gather Grove Communities can now tell when somebody has stopped being a facilitator here, so their Community Room seats can be reviewed rather than quietly outliving them.
* Nothing is removed automatically, deliberately: unpublishing a bio page to fix a typo must never strip somebody's room seat mid-term, and a facilitator who is also a parent belongs in the family rooms after they stop teaching. Communities asks you; it does not decide.

= 1.123.0 =
* The "next class" line in your community is now one calm line instead of a box — "Next: Vinyasa Flow, Wed 6pm", with a small calendar icon on a soft sand background. It takes about a third of the height it used to.
* It names your next class rather than the next three. Your full timetable is one tap away on the Schedule page.
* "3 spots left" is now a small rounded badge rather than grey text, so the class where it matters actually reads as urgent. It still only shows when fewer than five spots remain.
* "You're booked" stays quiet, and still replaces the spots-left badge — a class you're already in doesn't need to tell you it's nearly full.
* As before, the line disappears entirely when nothing is on the calendar, so a studio still setting up never shows an empty "no classes" box.

= 1.122.3 =
* Housekeeping: **bundles Gather Grove Core 0.35.0.** Adds the tick box that lets you mark a page you built yourself as part of your members' area — it appears in the Gather Grove box when editing a page. Nothing else changes on your site.

= 1.122.2 =
* Housekeeping: **bundles Gather Grove Core 0.34.0.** Brings this plugin onto the same shared Gather Grove code the rest of the suite runs, so every plugin agrees on which version it ships. Nothing changes on your site: this is the groundwork behind the shared member controls, and nothing here uses it yet.

= 1.122.1 =
* Fixed: **the tabs on the client and instructor dashboards now slide sideways on a phone instead of stacking up.** With several tabs in place, they used to wrap onto three or four lines and push a member's actual bookings most of the way down the screen.
* Your tab styling is untouched — the colours, the underline and the alignment you set all still apply on a desktop. On a phone the tabs simply start from the left so the first one is always reachable, and they are now big enough to tap comfortably.

= 1.122.0 =
* Added: **failed membership and payment-plan charges, waitlist joins and waitlist offers are now recorded on the shared Gather Grove activity stream**, so the rest of the suite can see them. Nothing changes on your site today — this is the groundwork for automated follow-ups, like a quiet nudge when someone's card fails or a note when a spot opens up.
* A waitlist offer is recorded as an offer, not a booking: the seat is held and the member emailed, but they still have to say yes.
* Nothing in this can affect a member's place in a queue. The offer is recorded only after it has actually gone out, so a problem elsewhere in the system can never quietly hand someone's held seat to the next person in line.

= 1.121.0 =
* Added: **a second adult can look after a family's children.** Until now only the paying parent could book, cancel or sign for a child — so a grandmother, a co-parent or a nanny who is genuinely involved had no way in, and families kept asking. A payer can now let any adult in their household manage the children, from the Household panel on their account.
* Added: that adult can do everything for the children — book, cancel, sign agreements, see the schedule — and nothing for the other grown-ups. Nobody gains any say over another adult's account.
* Added: adults looking after the same children can see **who the others are** — first names only, with the person who handles billing marked. Enough to know you are not the only one, so nobody has to ring you to ask whether an agreement is already signed. No contact details, no view of anyone else's classes.
* Changed: **grown-ups don't appear in each other's accounts.** A household account now shows the children and nothing about the other adults in it — their classes, agreements and documents stay their own, in both directions. The paying member still sees everyone in their Household panel and can remove them or change what they can do; they just don't see another adult's bookings alongside their children's.
* Changed: **what a family pays stays with whoever pays it.** A managing adult does not see the membership rate, the payment schedule, or the controls that change them; their card simply says who handles billing. If you run a sliding scale, a family's rate is nobody else's business, including a helpful relative's.
* Changed: a managing adult can still put a workshop or an extra day on their own card — only the recurring membership is tied to the payer.
* Changed: booking confirmations for a child now go to both responsible adults, so the person who booked and the person whose account carries it are both in the loop.
* Note: nothing changes for anyone until you grant it. No existing account gains access to anything, and only the payer can grant or remove it — so two adults can never lock each other out.

= 1.120.0 =
* Added: **no-shows are now part of a member's activity record.** Marking someone a no-show already updated your roster and triggered your follow-up, but nothing outside Booking could tell "did not come" apart from "not marked yet". It is now recorded as its own event, so retention and win-back follow-ups can act on it properly. A no-show is deliberately kept separate from a cancellation: a cancelled seat was given back, a no-show seat was held and wasted.
* Changed: the suite now tracks when each member was last active *anywhere* in Gather Grove — a class, a lesson, a community post. So a member who has stopped booking classes but is working through a course is correctly seen as still active, not as someone slipping away.

= 1.119.0 =
* Fixed: **an agreed rate of $0 was quietly charged at your suggested price instead.** If you recorded that a family pays nothing, every screen showed $0 while the subscription billed your suggested amount. Nothing looked wrong until a card was charged.
* Fixed: a family whose agreed rate is below your published minimum can edit their own rate again — previously they could not even re-confirm what they were already paying. Lowering it still means talking to you.
* Added: **a sliding-scale membership can now be genuinely free.** Tick "Allow $0" on the plan and a member can pay nothing at all, with no card asked for. It stays a real membership — it renews, the allowance resets, and they can raise their own rate later whenever they are able. $0 is a rung on the scale, not a way off it.
* Added: a plan can offer no recommended amount at all — the field opens blank instead of pre-filled, for a true pay-what-you-can. Plans where you did suggest a figure are unchanged.
* Changed: plans that allow $0 now say so — "Free, or pay what you can up to $1,200" rather than "Up to $1,200.00".
* Note: anything a member does choose to pay must clear 50 cents, the smallest amount card networks will process. So the range is "$0, or 50 cents and up" — nothing in between.

= 1.118.2 =
* Fixed: 1.118.1 accidentally included some unfinished work on pay-what-you-want packages that was still being written. It has been removed. Nothing you set is affected, and the dark-card fix from 1.118.1 is unchanged.

= 1.118.1 =
* Fixed: **a dark studio now gets dark cards.** If your studio uses a dark colour scheme — a dark page with light text — the cards drawn on top of it stayed white, so light text landed on white and account menus, checkout fields and the agreement box were hard or impossible to read. Cards, panels and borders now follow your page: choose a dark background and you get dark cards to match, in your own colour rather than a flat grey. Studios with a light colour scheme see no change at all.

= 1.118.0 =
* Added: **a pay-what-you-want class can now be genuinely free.** Tick "Allow $0" on the class and a booker can enter nothing at all — no card, no charge, recorded as a free booking. Until now the lowest anyone could actually pay was 50 cents, even when you set your minimum to zero, so a donation-based or free event could not be offered honestly. Applies to single classes and to whole courses.
* Note: because Stripe will not process a charge under 50 cents, the range this creates is "$0, or 50 cents and up" — someone can give nothing or give a dollar, but not a quarter. The form says so up front rather than refusing the amount after it is typed.
* Fixed: a class with a $0 minimum advertised that minimum and then refused the booking. Everything after the price check already handled a free booking properly; only the check itself treated $0 as a payment too small to take rather than as no payment at all.
* Fixed: a "From" price on a free class showed whatever the suggested price happened to be instead of $0.
* Changed: allowing $0 is a deliberate tick-box per class. Setting the minimum to zero on its own does not enable it, so nothing changes on classes you have already set up.

= 1.117.0 =
* Changed: **the Waivers tab in a member's account is now grouped by person.** A family sees a heading for each child with that child's agreements under it, and their own, marked as theirs. Previously it was one flat list where each row carried a small name chip — except the account holder's own rows, which carried nothing, so "no name" silently meant "yours". Families reasonably could not tell which was which. A member with nobody else on their account sees exactly the list they saw before, with no headings over a list that could not be about anyone else.
* Added: **"Applies to" on each waiver — everyone, or participants only.** On a family account, "Participants only" stops asking a parent for a copy of their own when they book for their children and never attend themselves; they carry on signing on each child's behalf. If that parent ever books a place for themselves, the waiver is asked for at checkout exactly as before — this changes who is *listed*, never who is *let through*. Existing waivers are unchanged and apply to everyone until you say otherwise.
* Added: **a waiver form now arrives with the participant's name already filled in.** When a waiver is signed by completing a Gather Grove Form, the link in a member's account is addressed to a specific person — so the child's name, and the signed-in parent's own name, email and phone, are filled in for them. It recognises the field names people naturally use (`child_full_name`, `guardian_name`, and similar); if your form uses different names, they can be mapped without renaming anything on a live form. Everything filled in stays editable, and which person the signed form is *about* is still established by the link itself, not by what is typed — so correcting a name changes the words on the page and nothing about the record. Requires Gather Grove Forms 1.13.0.
* Fixed: **a notification in your inbox is now clickable across the whole row**, not just the few words of its title, and opening one marks it read. Until now the only thing you could do to a single notification was dismiss it — "I have read this" and "throw this away" were the same button.

= 1.116.0 =
* Added: members can pay by bank (ACH Direct Debit) at checkout, not just by card — the purchase modal now uses Stripe's Payment Element, so whatever you enable in your own Stripe dashboard is offered at the point of purchase.
* Fixed: the first payment on a subscription was confirmed with a card-only Stripe call, which would have failed for any non-card method.
* Changed: the payment field's container no longer draws its own border — the Payment Element brings its own.
= 1.115.1 =
* Security: **passwordless sign-in links are now short-lived and never sent to administrators.** A magic sign-in link used to work for 24 hours; it now works for 15 minutes — long enough to arrive and be clicked, short enough that an old email in a forwarded or shared inbox is not a way in. Administrator and site-owner accounts no longer receive magic links at all and sign in with a password, so control of an admin's mailbox alone can never hand over the site. Everyday member sign-in is unchanged.

= 1.115.0 =
* Added: **Documents gets its own tab for the families who actually have documents.** It used to sit at the bottom of My Profile, which is fine when a studio has filed one or two things and hides them when a family has a dozen. Now, once a household has three or more, Documents moves up into its own tab; below that it stays folded into My Profile exactly as before.
* You do not configure this and there is no setting to find — it follows each family's own paperwork, so a family with twelve documents gets the tab on the same site where a family with none never sees it.
* Developers can change or disable the threshold with the `gather_grove/booking/documents_tab_threshold` filter.


= 1.114.0 =
* Fixed: **the Gather Grove section tabs ignored your brand colours on pages you built yourself.** If you placed the tabs with the `[gg_shell_sections]` shortcode on your own page, they came out in Gather Grove's default green instead of your studio's palette. They now carry your colours wherever they appear.

= 1.113.0 =
* Added: **a waiver can now be signed by completing a form.** A waiver on its own shows your text and takes one signature, which is not enough when what people agree to needs answers — initials beside each clause, consent choices, a child's details. Build the document in Gather Grove Forms, name it on the waiver, and "Review & Sign" opens your form instead. Everything else about the waiver is unchanged: it still appears in the member portal, still shows signed or not signed, still expires, still counts on the Waivers screen, and still gates booking.
* Added: **on a family account, the link is addressed to the household member it is about.** Each person gets their own row, and opening the form from that row records the submission against that child rather than relying on a name typed into a box.
* Added: **you can now require more than one waiver of everybody.** The studio-wide waiver setting takes several, so a studio asking for both a liability waiver and a photo-permission consent no longer has to attach the second to every class one at a time — which also meant families who had not booked yet never saw it. Your existing choice is carried over untouched.
* Note: bumping the version of a form-backed waiver asks people to fill the whole form in again rather than click once — right when you have genuinely revised the agreement, unfair when you are fixing a typo. The waiver editor now says so. A form-backed waiver is also never satisfied by a plain "I agree" tick anywhere, including the reminder-email signing page, because the agreement itself lives in the form.

= 1.112.0 =
* Added: **instructors are recognised as instructors by the rest of Gather Grove now.** If a facilitator puts "My Classes" in your site menu, it works — before this, Gather Grove had no way to ask Booking who teaches, so it assumed nobody did.
* Changed: **the panels other Gather Grove plugins add to your member and instructor dashboards now arrive through a shared Gather Grove hook**, rather than a Booking-specific one. Nothing moves and nothing looks different — this is plumbing, so that Communities and Courses stop having Booking's name written into them and keep working on sites that do not run Booking.
* For developers: `gather_grove/booking/client_dashboard_tabs` and `gather_grove/booking/instructor_dashboard_tabs` are **still live and still supported** — they now also carry whatever is registered on `gather_grove/portal/tabs`. Existing registrations need no change. Booking still validates, rejects reserved slugs and sorts, exactly as before.

= 1.111.0 =
* Added: **Account is now part of the Gather Grove section bar too.** Members get one row of tabs that reads Community, Classroom, Schedule, Account — so getting to their bookings, packages and invoices no longer means hunting through a menu. It points at the My Account page you already have; nothing moves.
* Added: **instructors get "My Classes" on the bar as well**, next to their own Account. Teaching does not replace having an account — instructors book classes and buy packages like anyone else, so they get both.
* Members never see the instructor page, and a studio that has not set up an instructor dashboard page simply does not get that tab — the Account tab is unaffected either way.

= 1.110.0 =
* Added: **your schedule is now part of the Gather Grove section bar.** If you run Gather Grove Communities or Courses, members get one row of tabs — Community, Classroom, Schedule — and Schedule now appears among them, pointing at your own Schedule page. It goes both ways: the same tabs now sit at the top of the Schedule, My Account, My Classes and Packages pages Gather Grove set up for you, so members can get back to the community from them instead of hunting through your menu.
* The tabs appear **only on pages Gather Grove created for you.** A page you built yourself is left exactly as you made it — drop the `[gg_shell_sections]` shortcode on it if you want the tabs there too.
* Nothing appears for logged-out visitors, and nothing appears if you have not told Booking which page is your Schedule (Settings → General → Schedule page). No tab is better than a tab that leads nowhere.

= 1.109.0 =
* Changed: **pre-class lobbies are now something a class asks for, rather than something every class gets.** If you use Gather Grove Communities, a lobby is the private room that opens half an hour before a session so the people booked in can say hello. Until now it opened before *every* session on the calendar, whether or not that was ever wanted — a room appearing before a Tuesday drop-in that nobody asked for and nobody used. There is now a **Pre-class Lobby** checkbox on each class (Classes → edit a class → Class Settings), **off by default**. ⚠️ **If you were relying on lobbies, tick the box on the classes that want one** — after updating, no new lobbies open until you do. It is worth ticking for a course cohort or a workshop where people arrive as strangers, and usually not worth it for a regular drop-in. Lobbies already open stay exactly as they are; nothing is deleted early and nobody is removed from a room they are already in.

= 1.108.0 =
* Fixed: **the Waivers tab showed every waiver on your site, even ones attached to a class the member had never signed up for.** A waiver attached to a particular class is only ever asked of people booked into that class — but the portal listed it for everyone, so families saw paperwork waiting for them that they would never actually be asked to sign. The tab now shows only the waivers that genuinely concern each person: the ones that apply studio-wide, and the ones attached to something they have actually booked. Anything already signed stays on the list, so nobody loses the record of an agreement they made. If nothing at all applies to a household, the tab no longer appears.
* Fixed: **on a family account, the Waivers tab in the member portal showed a parent only their own waivers — never their children's.** Waivers are recorded per person, and on a family account it is usually the child who has to be covered while the parent is the one signed in. Because the tab asked only about the person viewing it, a parent looking at their portal saw their own status and nothing else, with no way to reach — or even see — an agreement outstanding for a child. Children on a family account have no login of their own by design, so the parent is the only person who could ever sign: there was no way through at all. The tab now lists every member of the household, names whose row is whose, and lets a parent sign on behalf of a child. Households already worked this way everywhere else in the portal; this tab was the one place still asking the narrower question.
* Added: **a form can now be addressed to a particular member of a household.** A family agreement built in Gather Grove Forms asks for the child's name as ordinary typed text, so nothing connected the finished form to the child it was about — which meant a signed copy could not be filed to the right person, and two children with similar names were indistinguishable. You can now send a link that is specific to one person; the form they fill in records who it was for. The link says only which member the form concerns and is never treated as proof of who is holding it, exactly like the waiver reminder links. Requires Gather Grove Forms 1.12.0 or later.

= 1.107.0 =
* Changed: **your class schedule now follows members around the community, instead of sitting on the home page only.** The next few classes ride along in the left-hand rail on every community page — on a wide screen in the rail itself, on a narrow one stacked above the page. Same quiet rules as before: a note only when a class is nearly full or you're already booked, nothing at all when there's nothing scheduled, and never on a public landing page. Requires Gather Grove Communities 1.14.0 or later.

= 1.106.0 =
* Added: **your class schedule now appears on the community home.** When Gather Grove Communities is installed, the next few classes on your calendar show up right under the welcome — with the time, and a quiet note when a class is nearly full or you're already booked into it. Nothing shows on a public landing page, and nothing shows when there's nothing scheduled.

= 1.105.0 =
* Added: **groundwork for the community notification bell.** Sibling Gather Grove plugins can now read your in-app inbox — not just write to it — so an upcoming Communities release can show a bell with your unread notifications on community pages. Nothing changes in this plugin's own screens; your Inbox tab works exactly as before.

= 1.104.1 =
* Fixed: **bank-debit payments (ACH, SEPA, Bacs) could leave a booking waiting forever for its confirmation.** These methods do not clear at the moment of checkout — Stripe confirms them a few days later, through two notifications that the Connect Webhooks button was never subscribing you to. Everything looked correctly set up, because the connection itself was fine; the two messages carrying "the money actually arrived" were simply never sent. If you only take cards this never affected you. Click **Connect Webhooks** once to pick up the fix.
* Fixed: **clicking Connect Webhooks again now repairs your existing connection.** Previously, if a connection already existed, the button told you to delete it in your Stripe Dashboard and start over — which throws away a working signing secret to fix something small, and meant a site connected long ago was stuck with whatever event list shipped that day. It now compares your connection against what the plugin needs and quietly adds anything missing. Your signing secret is left alone and keeps working.

= 1.104.0 =
* Fixed: **"Mark Late Cancel" now actually cancels the booking.** Marking someone as a late cancel on the roster only wrote it on the attendance sheet — the booking itself stayed active and **kept holding its seat**. The person stayed on your next roster looking like they were still coming, and the seat they gave up was never offered to your waitlist or to a walk-in. Late Cancel now retires the booking and frees the seat, while still counting as a used slot in your reports and still not returning the class credit — the whole point of a late cancel. If you mark someone Late Cancel by mistake and then mark them Attended, that undoes it and takes the seat back.
* Fixed: **voiding an invoice now switches off its payment link.** Voiding (or expiring) an invoice only changed it on your side — the payment link already sitting in the client's inbox stayed live and payable for weeks, so a cancelled invoice could still be paid and you would have to refund it. Voiding now switches the link off, and the client sees "this link is no longer active" instead of a checkout page. If Stripe cannot be reached at that moment the invoice is still voided and the problem is written to your logs, so nothing gets stuck.
* Fixed: **the questions you ask before someone books no longer sit in a white box.** When you require an intake form before a booking, the form was drawing its own page background inside your booking panel — a hard-cornered pale slab, with a tall empty gap around a single short question. The questions now sit directly on the booking panel and take up only the room they need, so the whole thing reads as one form rather than a form inside a form. Applies wherever you ask them: classes, appointments, and the package shop.

= 1.103.0 =
* Added: **courses that allow drop-ins can now actually sell a single session.** The "allow drop-ins alongside enrollment" option has been in the class editor for a while, but the class page ignored it — every visitor was shown the full-course price and nothing else, so the only way anyone ever got a single seat was you booking it for them by hand. The course page now offers "Just want one session? Drop in for $X" underneath the enroll button, which takes the visitor to the normal booking form at your drop-in price, with a link back to the full course if they change their mind.
* Fixed: **a course with drop-ins turned on showed the whole-course price next to every single date.** A four-week $180 course with $48 drop-ins listed "$180.00" beside each individual week on the schedule and on class cards — which reads like $180 for that one night. Those classes now read "$180.00 series · $48.00 drop-in". Courses without drop-ins are unchanged.

= 1.102.0 =
* Added: **members can pay you by bank transfer, and switch to it themselves.** Turn on "Bank Payments (ACH)" in Settings → Payments (after enabling ACH Direct Debit in your Stripe dashboard) and members get an "Add a bank account" option in their portal alongside cards. Bank transfers cost a fraction of what cards cost on larger amounts, so every member who switches saves you money on every payment — and until now there was no way for them to switch without emailing you. Members whose bank supports it are set up instantly; everyone else is verified by two small deposits over a day or two, and the portal now says so instead of looking like nothing saved.
* Fixed: **choosing a different card did not actually change what you were charged.** If a member added a second card and chose it, the portal marked it as their default and the old card kept being charged anyway — because a membership pins its own payment method, and only the account-level one was being updated. Every screen agreed the switch had worked. Memberships now move to the newly chosen card or bank account, with no surprise charge for making the change.
* Changed: wording throughout the payment-methods panel no longer calls a bank account a card.

= 1.101.2 =
* Fixed: **removing a saved payment method went wrong for anyone paying by bank transfer.** The check that stops you from deleting the last payment method while a membership is set to charge it automatically was only ever counting cards. So a family who had moved to bank transfer could not delete the card they no longer use, a family with two bank accounts and no card could not delete either of them, and — the one that actually costs money — a family with a couple of cards on file could delete the bank account their membership charges, after which the next payment would fail with nothing to show why. Cards and bank accounts are now counted together, and the wording no longer calls a bank account a card.

= 1.101.1 =
* Fixed: **a member paying by bank transfer was told they had no payment method on file.** If you accept ACH / bank payments, the member's portal only ever looked for saved cards — so someone paying you every month by bank transfer saw "No card on file" and no way to see, choose or remove the account you were actually charging. Bank accounts now appear alongside cards, newest first, labelled with the bank's name and marked as a bank rather than being given a made-up expiry date.

= 1.101.0 =
* Added: **import your founding families without emailing any of them.** Paste your list — parents, children, the rate each agreed, and anything they have already paid — into the new "Import a founding cohort" panel on the Clients screen, and everything lands at once: parent accounts, each child as a household member, the agreed membership, and any money already handed over credited against their first invoice. Nothing is sent to anybody, so you can set your cohort up weeks before you open and send one deliberate welcome when you are actually ready. Preview first — the Import button stays locked until you have seen exactly what it would do — and re-running the same list is safe: nobody is duplicated and no payment is credited twice.
* Added: sign in with a magic link (shipped in the 1.100.0 update, described properly here) — members can log in with just their email address, no password to remember.

= 1.100.0 =
* Added: **your students can sign in without a password.** A member enters their email, receives a link, and clicking it signs them in — no password to set, forget, or reset. This is the single most common place a booking is abandoned: someone who joined a year ago comes back to book a class, cannot remember a password they may never have chosen, and gives up rather than wading through a reset. Under Settings → General you choose what your site offers: passwords as before, magic links only, or both side by side. Each link works once and expires shortly after it is sent, so a forwarded or leaked email cannot be reused. Off unless you turn it on; nothing changes for sites that leave it alone.
* Added: **a grace period for failed membership payments.** Each package can now set how many days a member keeps their booking access after a renewal payment fails (the "Grace period" field on the package, which previously wasn't consulted anywhere, now actually works). During the grace period everything behaves normally — booking, class credits, member pricing — and the moment the payment goes through, the window simply closes. Leaving it at 0 keeps today's behaviour: access pauses immediately.
* Added: **everyone hears about payment trouble, warmly, on both channels.** When a payment fails, when the grace window is about to end, when it ends, and when the payment finally goes through — the member and the studio each get an email *and* an in-app inbox note. The copy treats a failed card as what it almost always is (a bank hiccup, not a character flaw) and always names the way back in. The two timed emails are editable under Settings → Email → Templates.
* Added: **sliding-scale rate check-ins.** Every few months (per package, default 3; 0 = never), sliding-scale members get a gentle invitation to revisit their rate — anchored to each membership's own start date, not a shared calendar. Doing nothing keeps the current rate; silence always means continuity. The email is editable under Settings → Email → Templates.
* Added: members who change their own sliding-scale rate now get a confirmation naming the new amount and when it starts, and the studio is notified too — previously the change saved silently.
* Fixed: a membership with a payment problem no longer vanishes from the member's dashboard — the card stays, says plainly what is happening and until when, and points at the payment-method fold.

= 1.99.0 =
* Added: **record money a member handed over outside the site** — a cheque, cash, a bank transfer, a deposit taken before you were online. A new "Offline Payments" card on the member's admin page records the amount, and it comes off their **next membership invoice automatically**: someone who paid a $200 deposit is charged the remainder, someone who paid a month in full is charged nothing. Any credit already waiting is shown right beside the form (and named again before you confirm), so the same cheque never gets recorded twice by accident. If the recording fails partway, nothing is written anywhere — a clean retry is always safe.

= 1.98.0 =
* Added: **members on a sliding scale can change their own rate from their dashboard.** The membership card now states the rate plainly, and a "Change your rate" fold lets the member pick a new amount within the band — no approval needed, because a change inside the band was always their right. The change starts at the **next** renewal: nothing is charged today, the period already paid for is untouched, and until then the card says so honestly — *"Renews March 1 at $700 (you changed this from $650)"*. Below the band's floor stays a conversation with the studio rather than a form field.
* Changed: switching how often you pay now carries a scheduled rate change with it instead of quietly reinstating the old rate on the next invoice.

= 1.97.0 =
* Fixed: **a member with a staff-agreed sliding-scale rate could be billed the suggested amount instead.** The checkout window pre-filled the suggested amount and sent it as if the member had chosen it, which overrode the rate that had been agreed with them. On memberships that require approval the amount field now starts blank — leaving it blank pays the agreed rate, and typing a number works exactly as before.
* Added: **sliding scale now works when buying for someone else.** The person paying chooses the amount within the band; previously a gifted sliding-scale membership silently charged the package's fixed price.
* Added: **sliding scale now works on one-off packs**, not just recurring memberships. A pack with a band was already advertising "Sliding scale" on its card but charging the fixed price at checkout — the chosen amount is now honoured (and checked against the band) on every purchase path, including buying for someone else and buying a pack together with a class booking. With no amount entered, the band's suggested amount applies.

= 1.96.2 =
* Fixed: the last two places where this plugin still treated every Gather Grove admin screen as its own. Its "Message Participants" dialog markup was being added to sibling Gather Grove plugins' admin pages that never use it, and its "Install Elementor" notice could appear on their pages too — even though only Booking's client-facing pages need Elementor. Both now appear only on Booking's own screens (the notice also still shows on the Plugins screen).

= 1.96.1 =
* Changed: the unified Gather Grove admin pages now follow your studio brand fully — the shared pages this plugin hosts alongside its sibling Gather Grove plugins pick up your brand colors throughout.
* Fixed: this plugin's admin stylesheet no longer loads on the admin screens of sibling Gather Grove plugins, where some of its styling could bleed into their pages. Every Booking screen keeps its styling; only screens belonging to other Gather Grove plugins are affected.

= 1.96.0 =
* Added: sliding-scale memberships now ask the member to choose their amount at checkout. The purchase window shows a "Choose Your Amount" field pre-filled with your suggested amount and held to the band you published; previously the range was displayed but checkout quietly charged the suggested amount. The price shown in the window always matches the amount that will actually be charged.

= 1.95.2 =
* Fixed: the sidebar showed two entries both named "Waivers." The roster compliance screen is now labeled "Waiver Compliance," matching its own page heading; the waiver-documents list keeps the "Waivers" name.
* Fixed: on a pay-what-you-want class, applying a family promo code validated the discount against the drop-in price instead of the amount the booker actually chose.
* Fixed: the admin welcome tour's first steps pointed at a menu name retired in an earlier rename, so the tour opened without highlighting the Gather Grove menu it was describing.
* Added: a "Clear Cancelled Sessions" button on the Sessions screen removes cancelled sessions that never had a booking — the leftovers of rearranging a schedule before anyone signed up. Cancelled sessions that had bookings are always kept; those are part of your studio's history.

= 1.95.1 =
* Security: a session belonging to an unpublished class now returns exactly the same "not found" response as a session that doesn't exist, so browsing session ids reveals nothing about unpublished classes — not even that one is there.

= 1.95.0 =
* Security: a hardening pass across the plugin's public surfaces. Draft (unpublished) classes, waivers, and page settings can no longer be read through public booking endpoints by guessing ids; the guest waiver-signing credential is now a fully opaque, expiring, server-verified token that reveals nothing about any account; the waiver-signing endpoint is rate-limited and never records duplicate signatures; and every database query fragment was audited so no prepared statement is ever assembled from another. No settings or data change; nothing to do after updating.

= 1.94.1 =
* Fixed: **only the first appointment on a site could ever be booked** — every later appointment failed with "Could not reserve the session for this slot." The slot's internal session record was created without its unique identifier, so the second one ever collided with the first and was silently discarded. Latent for several versions; surfaced on the first 1.94.0 install.

= 1.94.0 =

= 1.93.1 =

= 1.93.0 =
* Added: **a Waivers screen that answers "who still owes me a signature?"** Until now you could see one client's waiver history at a time, but there was no way to see the whole roster at once — which is exactly what you need the moment you update a waiver. Find it under Gather Grove → Waivers. It shows, for each active waiver, who has signed the current version, who signed an older one, whose signature has expired, and who has never signed at all — with the reason for each, and a CSV export.
* Added: **waiver reminder emails you send by hand.** Tick the people you want to chase, review a list of exactly who will be emailed, then send. There is no automatic schedule and never will be — a waiver reminder that fires on its own turns a formality into nagging.
* If someone has an account with you, their reminder link asks them to log in before signing, so a signature is always attached to the right person. People without an account (a child on a family account, for instance) can sign straight from the link.
* The screen tells you plainly how many people have **no email address on file** — they can't be reached by any reminder, so you'll want to collect those in person. You can record a paper signature against anyone directly from the list.
* Fixed: a class-specific waiver in the sample/demo content was never actually being applied.

= 1.92.0 =
* Fixed: **a package sold for a course or training could be spent on classes instead — and never ran out.** If you sell something that grants access to a course rather than a number of class sessions, it was being treated as an ordinary class pack. Two defaults then combined badly: a pack with no specific classes listed counts as valid for *every* class, and a pack with no session count counts as *unlimited*. The result was that your most expensive product quietly doubled as a free, never-expiring pass to every class on your schedule, and there was no setting that would stop it.
* The **"Access pass"** tickbox on a package now does what it says on any package, not only on memberships — tick it on a course or training package and it can never be offered as payment for a booking, never spent on one, and never auto-books its holder into sessions. Nothing changes for your ordinary class packs and memberships: without that tickbox, they behave exactly as before.
* If you sell a course or training package, open it and tick **Access pass** — that is the one thing worth checking after this update.

= 1.91.0 =
* Fixed: **"Cookie check failed" — if your site uses page caching, new visitors could be blocked from signing up, logging in, booking as a guest or buying a membership, all at once.** The message blames the visitor's browser, which is why this is easy to lose days to: nothing is wrong with their cookies, and nothing is wrong with your site's settings. Gather Grove put a short-lived security token into each page. That token expires after 24 hours — but a page cache can keep serving the same page for far longer, so the page still looked perfect while every form submitted from it was quietly refused. We found this on a live studio whose signup page had been served from cache for three days; a family had to text them to report it, because nothing showed up in any log.
* Note: the token was never protecting anything on those pages — there is no account to protect until someone signs in — so it is simply no longer sent to signed-out visitors, and the pages are immune from now on. Nothing changes for signed-in members except that a page left open overnight now quietly renews itself instead of failing on the next click. There is nothing for you to configure, and no need to change your caching.
* Fixed: **the booking form's spam trap had never actually caught anything.** Gather Grove has placed a hidden decoy field in the booking form for a long time — the kind only an automated bot fills in — and the server has always known to reject anything that filled it. The two halves were both there and correct; nothing was connecting them, so the answer never reached the server and every submission passed the check. The trap now works as described, and a second one has been added alongside it: a form submitted faster than a person could plausibly have read it is turned away. Both are quiet by design — a real visitor will never notice either, and neither depends on your caching.
* Fixed: **emails were hard to read for anyone using dark mode.** In a dark inbox — Gmail on a phone especially — the mail app would darken the message background but leave the text colours alone, so grey-on-white body copy became grey-on-black, and the button label all but disappeared. Your emails now carry a proper dark palette of their own, so they stay legible whichever theme your students read them in. Light mode is unchanged, and your own button colour is still yours.
* Changed: **appointment confirmations now show a real reference code.** The confirmation used to show its internal row number, which meant your very first booking was labelled "Reference #1" — it looked like something unfinished, and on a busy studio it quietly told every customer how many bookings you had taken. Confirmations now show a short code (like `3F9A-21C4`) that means nothing except "this booking".

= 1.90.3 =
* Changed: internal housekeeping — two stored settings were renamed to use Gather Grove's full name, matching WordPress.org's guidance on plugin prefixes. Your document encryption key is carried across automatically, so documents you have already filed keep opening exactly as before and there is nothing for you to do.

= 1.90.2 =
* Fixed: **sample data was sending real email, and it all bounced back to you.** If you had turned on Demo Mode, the sample students that come with it were being sent genuine class reminders. Nobody receives them — the sample addresses are deliberately fake — so every one bounced straight back into your inbox. Worse, a steady trickle of bounces is exactly what email providers watch when deciding whether your real messages are trustworthy. Gather Grove now recognises the reserved addresses used for sample and test data and quietly skips them, so your sample studio behaves like a real one on screen without any mail leaving the building.
* Note: your real students are unaffected. Only addresses ending in the domains reserved worldwide for testing (`.test`, `.invalid`, `.localhost`, `.example`, and `example.com`) are skipped — those can never receive mail from anyone.

= 1.90.1 =
* Fixed: the new invoice receipt showed the invoice number twice when the invoice had no separate description.

= 1.90.0 =
* Fixed: **paying an invoice sent no receipt.** Buying a class pack, a membership or a course has always sent the buyer a receipt, but paying an invoice sent nothing — and Stripe did not send one either, because invoice payment links are not set up to. Anyone who pays an invoice now receives a receipt automatically, showing the invoice number, what it covered, the date and the amount.
* Security: **files you upload to a client are now encrypted on disk.** The previous release let you move them outside your website folder, which is the strongest fix — but not every host offers somewhere to move them to. Encryption protects them everywhere, with nothing to configure: the file stored on the server is unreadable on its own, and is only turned back into a document when someone with permission downloads it through Gather Grove.
* Note: **your encryption key now belongs in your backups.** Gather Grove creates one for you automatically and the System Status screen shows you where it lives. If you keep a full backup (database included) you already have it. If you ever restore files without the database, documents will not open — so keep the two together.
* Note: documents saved before this update are not touched and keep opening normally. They can be converted whenever you are ready, and each one is checked before the original is replaced.
* Changed: a second protection file (`web.config`) is now written for Windows/IIS servers, alongside the existing one for Apache.

= 1.89.0 =
* Security: **files you upload to a client could be opened by anyone who had the link.** Those files are meant to be readable only through Gather Grove, and the folder they live in carries a rule telling the web server to refuse direct access. That rule works on Apache servers but is ignored by nginx, which most managed WordPress hosting uses — so on those hosts the file would simply download. Nobody can browse the folder or guess a filename (they are long random strings), so this needed someone to already have the exact link. But a link can leak through a backup or a server log, so this is now fixed properly.
* Added: **a new "Document Storage" section on the System Status screen tells you where your files are kept and whether your server will hand one to a stranger.** It checks by actually trying to fetch a harmless test file the same way an outsider would, so you get the real answer for your host rather than an assumption. If it finds a problem it shows you exactly what to change.
* Added: your files can now be stored in a folder outside your website directory, where no web address can reach them at all. Your host or developer sets the location; existing files can then be moved across safely, each one checked afterwards to confirm it arrived intact.
* Note: nothing moves on its own when you update. Moving files automatically could break access to them if the new location were not writable, so Gather Grove tells you what it found and leaves the change to you.

= 1.88.0 =
* Added: **your members can now see the documents you file to them.** Until now, anything you uploaded to a member's record was visible only to you and your staff — the member had no way to open it. A "Documents" section now appears on their profile, listing what you have shared, newest first. If you have never filed a document, nothing appears and nothing changes.
* Fixed: **a parent could not open their own child's paperwork.** Downloading a document required a staff permission, so the only people who could read one were the people who uploaded it. A parent can now open documents filed to anyone in their household, which on a family account is usually the child rather than the parent. Staff permissions are unchanged.

= 1.87.0 =
* Added: **groundwork for issuing a document to your members, and for filing a signed one back to them.** Until now, the only way a file could reach a member's profile was for a person to upload it there by hand, one member at a time. Gather Grove can now file a document it holds or produces itself — which is what a handbook sent to everyone, or a signed agreement returned to the person who signed it, both need. This release lays the foundation; you will not see anything new on screen yet.
* Changed: uploading a document and filing one now apply exactly the same rules about what is allowed — the same file types, and the same 10 MB limit — so the two can never quietly disagree.
* Changed: an empty file is now refused rather than stored as a document with nothing in it, and a file whose contents do not match its extension is refused rather than let through.

= 1.86.0 =
* Added: **your class pages now tell Google what they actually are — a real class, on a real date, at a real price.** Until now a search engine could only see a generic web page. Each class page now also carries the details behind the scenes: the upcoming dates, the price, the instructor, the venue and how many seats are left. That is the information Google needs before it can show a class with its date and price directly in search results, or list it among events near you. Nothing changes visually on your site.
* Note: multi-week offerings (courses, enrollments and retreats) are described as one course covering the whole run, rather than as separate events for each week — that is what someone is actually signing up for. Drop-ins, workshops and pop-ups are described one date at a time.
* Note: a class with no upcoming dates says nothing rather than something empty, and private classes are skipped entirely, exactly as they already are for search engines.
* Note: this is drawn from what you have already filled in. A class shows a price if it has one, a venue address if the location has one, and a photo if it has a featured image — so the more complete your class and location details, the more Google can show.
* Note: while Demo Mode is on, nothing is published to search engines — your sample classes are never advertised as real events. A class you have marked as taking real bookings still publishes, so a live offering and the demo sandbox can sit side by side.
* Fixed: **a long enough email could break the page that triggered it.** When several emails went out at once, or a failed send was retried, the whole email was handed to the background queue — which has a size limit it refuses to exceed. A normal booking confirmation is comfortably over that limit, so the error could surface on the very request that created the booking. Queued emails now store their content separately and hand the queue only a reference, so length no longer matters. Emails already waiting in the queue when you upgrade still go out normally.
* Fixed: **instructors with no contact details silently stopped receiving notifications.** If an instructor had neither an email address nor a linked WordPress user, every booking, cancellation and reminder email addressed to them was skipped without warning — the studio had no way to find out. Gather Grove now tells you, with an admin notice listing exactly which instructors are affected and a link to fix each one.

= 1.85.2 =
*(Never released — its fix ships in 1.86.0 above.)*
* Fixed: **emails carrying an attachment could fail to send, or arrive with the attachment missing, depending on your SMTP plugin.** Attachments were passed to the mail layer as in-memory content rather than as a file. Most SMTP plugins re-read attachments expecting a file path — so with FluentSMTP, for example, an email with an attachment either failed outright (on its SMTP option) or arrived with the attachment quietly stripped (on SendGrid, Mailgun, Postmark, Amazon SES and similar). It went unnoticed because emails *without* attachments kept sending normally. Attachments are now written to a real temporary file and passed as a path, which every mail plugin handles correctly. No Gather Grove email currently sends an attachment — calendar invites have been sent as a link since 1.70.0 — so nothing changes in what you receive today; this clears the way for future attachments to work reliably.

= 1.85.1 =
* Fixed: **the admin Dashboard could show a blank page.** If a booking pointed at a session that had since been deleted, the Dashboard's recent-bookings table hit a missing date and stopped rendering the whole screen — with nothing in the logs to explain it. A single stale booking was enough. Rows with a missing date now simply show no date, and the rest of the page loads normally. This affected earlier versions too.

= 1.85.0 =
* Changed: the plugin's internal names for classes, instructors, locations, rooms, services, packages, invoices, waivers and the class taxonomies moved to a longer, more distinctive prefix, at WordPress.org's request. Your site is migrated automatically and nothing you have set up changes meaning.
* Added: if an Elementor Theme Builder template or Loop Grid still points at an old internal name, you'll get a notice listing exactly what to re-pick. It only appears if something is actually affected.
* Fixed: signing a waiver as a guest now requires a credential the site issues during checkout, rather than accepting an email address as proof of identity. Signed-in members are unaffected.
* Fixed: bulk actions on the Bookings and Invoices screens confirm your permission and the security token before acting on anything.
* Fixed: assorted code-quality and security-hardening work from the WordPress.org review.

= 1.84.1 =
* Fixed: a nickname in a member's name no longer turns into punctuation in their account monogram. A member recorded as "Annabella (Ella) Folger" was initialled "A(" instead of "AE". The monogram now reads the first letter of each part of a name and skips anything that has no letter in it. Only visible to members who have not set a photograph.

= 1.84.0 =
* Added: **the Login widget can now be a header account menu.** Logged-In State gains an "Account Menu" option — the member's avatar and first name as a compact pill that opens their own links (My Account, Inbox, My Classes, Community, Packages & Memberships, Log out). Rows are typed rather than free links, so each finds its own destination from your settings and hides itself when there is nothing behind it: no Memberships page configured, no Communities installed, or a viewer who is not an instructor. You never end up with a menu row pointing at a missing page.
* Added: unread notifications show as a count on the closed pill, so a member can see something is waiting without opening the menu.
* Added: members with no photograph now get a two-letter monogram in your brand colour instead of the grey Gravatar silhouette.
* Added: **a quieter logged-out trigger.** Button + Modal mode gains a "Text Link" style, for headers that already carry a primary call to action.
* Note: the menu opens, closes and takes keyboard input without JavaScript, so it keeps working on pages where a page builder's front-end does not initialise.

= 1.83.1 =
* Fixed: buttons and dark accents now use your own colour on hover when the plugin is following your site's palette. The hover shade was fixed to the Gather Grove green instead of being derived from your palette, so a button could sit in your brand colour and turn green the moment you moused over it. The soft accent wash had the same problem. Both are now mixed from the colour your site actually resolved to.

= 1.83.0 =
* Fixed: **a parent could not pay for a membership the studio had already approved for their child.** With family accounts the child is a dependent with no login of their own, but checkout asked whether the person signed in held the approval — which on a household is nobody. Families were shown "Request Membership" on a plan already agreed with them, and submitting anyway was refused, so there was no way through. An approval held by anyone in the household now opens checkout for the person paying, and the membership is created for the child it was approved for while the parent is billed as before. Studios where members buy their own memberships are unaffected.
* Added: **a "Who is this for?" step at membership checkout**, shown only when more than one member of a household has an approved membership waiting on the same plan. With one member waiting the question is not asked. The rate and attendance days recorded for the chosen member are the ones applied.
* Added: **families can correct their own dependents' details.** A dependent's date of birth, phone and address could only be set when they were first added, so a studio that added families without birthdays could never collect them afterwards. The household panel now edits a dependent's name, date of birth, phone and address, and shows which members still have no date of birth on file. Email stays with the studio — it is an identity and a delivery address that waiver and booking mail route through — and members with their own login still manage their own profile.

= 1.82.1 =
* Fixed: **a class with no price of its own advertised "$0.00"** on cards, session lists, the schedule and calendar popovers — so a members-only class told the public it was free when it cannot be booked without a membership. Unpriced classes now show nothing, and a members-only class with no price of its own says "Members only". Classes that do have a price are unchanged, including a deliberate $0.00, pay-what-you-want "From ..." pricing, and whole-course enrollment prices.

= 1.82.0 =
* Added: **appointment times now read in the visitor's own timezone.** Slot times were shown in the studio's timezone with no timezone label, which is right for an in-person class but misleading for a remote one — someone in Boston booking a Denver studio's online consultation read "10:00am" as their own and would have arrived two hours early. Times are now shown in the visitor's timezone, with a note saying so and a toggle back to studio time. The note appears only when the two timezones differ, so a local visitor booking a local class sees no change at all, and the site's 12- or 24-hour time format is preserved.

= 1.81.1 =
* Fixed: **a parent could not book a members-only class their children were members of.** With family accounts the parent holds the login and the children hold the memberships, but the gate asked only whether the person viewing the page held one — so every paying parent met a "members only" wall and never reached the form to choose a child. The booking form, the appointment form and the "viewer has a membership" dynamic tag now consider the viewer's whole household. Logged-out visitors still see the login prompt, and someone with no membership and no dependents still sees the members-only message.

= 1.81.0 =
* Changed: **Demo Mode is now per-service instead of all-or-nothing.** Services and classes gain a "Take real bookings" option (shown whenever Demo Mode is on). Tick it and that offering books for real — charged normally, confirmation sent, video link created, and left alone by the nightly cleanup — while the rest of the site stays a sandbox. Previously the only choice was "everything is a demo" or "nothing is", so a site showing off a demo catalogue could not also take one genuine booking. Nothing changes unless you tick it.
* Fixed: demo appointment slots were not flagged as demo data, so they survived the nightly cleanup that removed the bookings attached to them — leaving orphaned slots to accumulate.
* Fixed: a transaction took its demo flag from the site-wide switch rather than from the booking it paid for, which could have deleted the record of a real payment overnight.

= 1.80.0 =
* Changed: **the member portal has fewer tabs.** Household and notification preferences now sit inside **My Profile** as sections you can open — they are both settings about you, which is what that panel already was.
* Changed: the misnamed "Notifications" tab is gone. It was always the *preferences* screen; your actual notifications live in **Inbox**. The section is now called **Notification preferences**.
* Changed: saved cards sit behind a **Payment methods** section on My Memberships, closed by default — managing a card is an occasional errand, and it now matches the "Change how you pay" section beneath it.
* Note: if you have switched My Profile off, Household and Notification preferences keep their own tabs as before.


= 1.79.0 =
* Fixed: **parents could not see their family's bookings, packages or enrolments.** If you use Family Accounts, a child's bookings belong to the child while the parent is the one signed in — so the parent's own portal showed nothing at all in Upcoming, Past, Enrollments, Active Packages, Payment Plans and Waitlist. All six now show the whole household, and each entry says which family member it belongs to. This is the same fix memberships received in 1.77.0, applied to the rest of the portal.
* Changed: the "Show all" button on upcoming bookings now accounts for a whole household, so a family with several children can still reach their full list.


= 1.78.0 =
* New: **"Auto-enroll members in these classes"** — an optional setting on any membership. When someone joins, they are booked into every upcoming session those classes hold, so they show up on your registers without anyone clicking through hundreds of sessions. Off unless you turn it on: for a drop-in studio it would fill your classes with people who are not coming.
* New: **attendance days per member.** Tick the days a part-time member attends when you approve them, and auto-enroll books them into those days only. Leave them all clear for someone who comes whenever the membership allows.
* Enrolment happens in the background, so a member joining a long term does not wait on it, and a nightly check catches anyone who was missed. A full class still refuses rather than being overbooked, and an unsigned waiver is flagged for collection at the first visit instead of blocking the booking.

= 1.77.0 =
* New: **members can save and manage a card from their account page.** Previously the only moment a card could be captured was during a purchase — so anyone paying by invoice could never switch to automatic payments, and anyone whose card expired had to contact you to fix it.
* New: members can see which card is on file, choose which one future payments use, and remove one they no longer want. An expired card is labelled as expired before a payment fails, not after.
* Fix: **a parent could not see their family's memberships.** If you use family accounts, the membership belongs to the child while the parent pays — and the account page only ever showed your own. Parents saw an empty page instead of what they were paying for. It now shows everyone in the household, with each child named.
* Removing a card is refused when a membership is set to charge it automatically and it is the only one on file — otherwise every future payment would fail quietly. The message explains what to do instead.

= 1.76.0 =
* New: **let members pay manually instead of storing a card.** Stripe emails them an invoice each period and they pay it themselves — nothing stored, nothing charged automatically. It is also the only way to start a membership for someone with no card on file at all: someone paying by cheque, or who has already paid you in cash.
* New: **let members pay every two weeks.** The amount is worked out so the year costs the same either way — a $650/month membership is $300.00 every two weeks, not $325.00. (There are 26 fortnights in a year, so charging half the monthly rate would quietly add a thirteenth month.)
* New: **"Change how you pay" in the member portal.** Members switch between monthly and fortnightly, or between automatic and invoiced, without cancelling and starting again — same membership, same renewal date. Changes take effect at the next payment; nothing is charged on the day.
* Both options are off unless you turn them on, per membership.

= 1.75.2 =
* Fix: **a child who held a membership could not book the classes it covered.** If you use family accounts, a child is attached to a parent's account rather than having their own login — and the membership check was looking at the login, not the child. Every dependent was refused, however valid their membership. Adults booking for themselves were never affected.

= 1.75.1 =
* Fix: a sliding scale that starts at zero showed "$0.50 – $1,000.00" on the plan card, because card payments cannot be less than 50 cents. It now reads "Up to $1,000.00", which is what you actually mean. A scale with no upper limit either reads "Pay what you can".

= 1.75.0 =
* Fix: **a sliding-scale plan advertised one price instead of its range.** A membership set to $500–$800 showed the public "$650.00/month" — the suggested amount looking exactly like a fixed price — and the note you wrote explaining what the range means appeared nowhere on your site. Plan cards now show the range, label it "Sliding scale", and include your explanation. Fixed-price plans are unchanged.
* New: **Age Range on plans.** Optional text like "Ages 5–12", shown on the plan card so people can see who it is for before they buy. It is a label, not a rule — nothing is blocked by age.

= 1.74.1 =
* Fix: an instructor whose term starts more than 30 days out saw "You have no upcoming sessions" on their schedule — because the panel only looks 30 days ahead by default. It now says which range it searched, tells them the date of their next session, and offers a one-click link to show everything upcoming.

= 1.74.0 =
* New: **sell ahead of a term.** Set the date recurring billing should begin on a membership, and people can join and pay now while the monthly cycle starts when your term does. Optionally charge them for the first period straight away, so you are not carrying a term you have not been paid for.
* New: **record a payment someone made offline** — a cheque, cash, a bank transfer, a deposit taken before you were online. It is credited to their account and comes off their next invoice automatically. Someone who already paid the first month is simply charged nothing; someone who paid a deposit is charged the difference. No refunds, no hand-edited invoices.

= 1.73.0 =
* New: **Approve a membership** for someone who signed up in person, over the phone, or by email instead of through your website. Find it on the Membership Requests screen. They can pay straight away — nothing is charged when you approve.
* You can record the amount you agreed with them on a sliding-scale membership, and it is filled in for them when they pay. You may set an amount below the published minimum here; that is the point of having agreed it together.
* Approving does **not** email anyone unless you tick "Let them know", so you can get people set up quietly and send your own welcome when you are ready.

= 1.72.0 =
* Tidier Stripe: each membership plan now has **one product in your Stripe dashboard** instead of a new one for every person who signs up. If you sell memberships you may have noticed your Stripe product list filling up with duplicates — that stops now, and Stripe's own per-product reporting starts being useful. Existing subscriptions keep billing exactly as before and are not touched; the old entries stay put because live subscriptions still point at them.
* Rename a plan and Stripe follows, so future invoices show the current name.

= 1.71.0 =
* New (optional, off by default): **sliding-scale pricing for memberships**. Set a range instead of a single price and let members choose what they pay within it — the range IS the price, not a discount off one. Built for co-ops and community programs where every family pays what they can. Find it under Sliding Scale when editing a membership. Packages you don't switch on are completely unaffected.
* New: **Billing Schedule** settings on packages — set a date for recurring billing to begin, so you can sell ahead of a term start. People join and pay now; the recurring charge starts on the date you choose.
* New: a **grace period** setting — keep someone's access open for a set number of days after a payment fails, before they lose their place. A failed card is usually a bank problem rather than a person problem. Set to 0 (the default) nothing changes.
* This release lays the groundwork; the member-facing screens for choosing and changing an amount arrive in the next one.

= 1.70.1 =
* Fix: the button in your booking-confirmation email now uses the wording and link you set in Settings → Email → Templates → Booking Confirmed. It had been ignoring both and always saying "Add to Google Calendar". If you never customised those fields, nothing changes.

= 1.70.0 =
* **Important fix — booking and appointment confirmation emails were failing to reach customers on some sites.** If your site sends mail through FluentSMTP (or another plugin that takes over WordPress email), the calendar invite attached to confirmation emails could stop the whole message from being delivered. Your own "New booking" staff email has no attachment and kept arriving, so nothing looked wrong — while the customer never got their confirmation. Please check your mail log for failed sends with a "Booking Confirmed" or "Appointment Confirmed" subject.
* The calendar invite is now a **link in the email instead of an attachment**, so no mail plugin can break the send. Customers get the usual "Add to Google Calendar" button plus a new "Add to Apple Calendar or Outlook" link that downloads the invite.
* Calendar invite links stop working once a booking is cancelled, and never work for sandbox (demo mode) bookings.

= 1.69.1 =

= 1.69.0 =
* New (for the Gather Grove suite): your Custom Labels — what you call Classes, Instructors and Appointments — are now shared with the other Gather Grove plugins, so renaming Instructors to Facilitators here changes the word in Gather Grove Communities' room badges and forms too. Nothing changes about how you set them: Settings → General → Custom Labels, exactly as before.

= 1.68.0 =
* New: an **Inbox** on both the client account page and the instructor dashboard — small in-site notifications you can dismiss one by one or mark all read. The tab shows how many are waiting ("Inbox (3)").
* New: milestone notes land in the Inbox automatically — your first class, then your 10th, 25th, 50th and 100th. Your instructor gets a quiet heads-up at the same moments, so they can actually say "that was your tenth class!" in person.
* New: birthday notes — a birthday line in the member's own Inbox, and a same-day heads-up to the instructors currently teaching them.
* New (for developers): other Gather Grove plugins can put notifications in the Inbox through a small stable interface, so community mentions and similar events can reach people here.
* Fixed: birthday greetings only worked for clients whose birthday was entered by an admin through certain screens — a birthday typed into your own profile was silently ignored. Both storage paths now count.

= 1.67.0 =
* New: a **My Classes** tab on the Instructor Dashboard. The schedule shows who is coming to a particular session; this tab shows who is in each of your classes at all — the enrolled cohort of an enrollment class (fixed cohort or open enrollment) and the recurring faces of a drop-in class, each with contact details, how many times they have attended, and (for cohorts) their progress through the course. Classes you lead, substitute for, or co-teach all appear.
* The tab honours your studio's custom Class label ("My Workshops" if you renamed classes to Workshops).

= 1.66.0 =
* New: dashboard tabs now have their own web addresses. Both the client account page and the instructor dashboard read the address bar on arrival, so `/my-account/#bookings` or `/instructor/#profile` opens straight to that tab, and pressing a tab records it — so a link you copy from your browser takes someone to what you were actually looking at. This applies to every tab, including ones added by other Gather Grove plugins. Previously a tab could only be described in words, never linked to, which meant a site could not put a portal tab in its own menu or an email.
* The back button still leaves the page rather than stepping backwards through the tabs you pressed.

= 1.65.0 =
* New: other Gather Grove plugins can now add their own tabs to the **Instructor Dashboard**, the same way they already can on the client dashboard. This is what lets Gather Grove Communities put a Community tab in your instructors' own portal — until now the only place community rooms appeared was the client account page, which is not where facilitators work, so the people running the rooms were the one group who could not reach them.
* The instructor tab bar now appears whenever there is more than one place to go, rather than only when the Profile tab is switched on.

= 1.64.0 =
* New (free): waitlist auto-promotion — when a spot opens in a full class, the next person on the waitlist is automatically emailed an offer with a hold on the seat. Previously a paid feature; now included for everyone.
* New (free): multiple recurring schedule blocks per class (e.g. Mon/Wed/Fri at different times). Previously a paid feature; now included for everyone.
* New (free): bulk attendance marking — mark a whole roster present in one action. Previously a paid feature; now included for everyone.
* Security: guest waiver signing no longer accepts a signature for an existing registered account based on a submitted email alone — the endpoint now asks those users to log in first. Genuine guest checkouts are unaffected.

= 1.63.1 =
* New: the photos you have already uploaded for your instructors and clients can now be used as their picture elsewhere on your site. Gather Grove Communities 0.17.0 picks this up automatically, so a facilitator's headshot and a family's photo appear in community rooms, beside their posts, in rosters and anywhere else a member's picture is shown — instead of the grey silhouette that appeared before while the photograph sat in your media library. Nothing to configure, and nothing changes if you don't use Communities.

= 1.63.0 =
* Access is granted the moment the buyer returns from checkout — the plugin verifies the payment with Stripe server-side on the return page, so plans work even on sites that haven't registered a Stripe webhook endpoint yet. The automatic plan lifecycle DOES need the webhook: register your Stripe webhook endpoint (Settings → Payments) and subscribe it to checkout.session.completed, invoice.payment_succeeded, invoice.payment_failed, and customer.subscription.deleted to get installment receipts ("Payment 3 of 10"), automatic pausing of access while a failed installment is retried (with an update-your-card email), automatic reopening once it clears, and the Paid in full / Defaulted end states. This also applies to the one-time hosted checkout, which now settles on buyer return too.
* Plan installments count toward revenue in Reports as they are collected, like membership renewals. The buyer's dashboard shows a plan card with deposit, progress, and status. Promo codes and buying-for-someone-else are not available on plan checkouts (they remain available on the one-time price). Defaulted plans keep their records for reporting; access can be restored manually with a comp package.

= 1.62.3 =
* Fix: logged-out visitors can now complete course checkout. Guest sessions sent Stripe an empty customer email, which Stripe rejected — every logged-out click on an enroll link ended in "Could not start checkout."

= 1.62.2 =

= 1.62.1 =
* Fix: checkout no longer silently charges a card when the person being booked already owns a membership or class pack that covers the class. The server now answers with a "you already have credit for this class" choice — use the credit (logging in first if needed, returning straight to the booking), or deliberately pay by card. Previously the card was charged whenever the payment dropdown hadn't refreshed after email entry (browser autofill can skip the events that trigger it) or when a lapsed login session submitted the booking as a guest.
* Fix: guest checkout also re-checks owned credits on the email field's change event, so autofilled addresses populate the payment options more reliably.
* Fix: membership- and pack-funded bookings no longer create an abandoned full-price Stripe PaymentIntent alongside the credit redemption. These were never charged, but each one cluttered the Stripe dashboard as an "incomplete" payment.

= 1.62.0 =
* Fix (security): the direct course-checkout link now enforces application gates too — previously a shared or guessed checkout URL could reach Stripe payment for a gated package without an approved application. Denied visitors are sent to the application page instead. Site developers: two new public filters let the Courses plugin veto course-level checkouts (see docs/booking-contract.md §4).
* Fix: editing a service's Description on the service edit screen now updates the public site. Previously the Sessions page and appointment booking widgets kept showing the old text no matter what was typed into the Description field.

= 1.61.1 =

= 1.61.0 =
* New: automatic Zoom meetings for virtual classes and appointments. Connect your own Zoom app (Server-to-Server OAuth) under Settings → Integrations and every confirmed virtual booking gets a real Zoom meeting created for its session — the join link flows straight into confirmation and reminder emails ({virtual_url}) and the dashboard's Join Online button. Meetings are updated on reschedule and removed on cancellation; each instructor can host under their own Zoom email.
* New: confirmation emails now carry a real calendar invite (.ics attachment) — Gmail, Outlook, and Apple Mail show their native add-to-calendar strip for the booked class or appointment.
* New: instructors get a personal calendar feed link — subscribe from Google Calendar, Apple Calendar, or Outlook and upcoming classes and appointments appear automatically (rolling 90-day window). The link is found on the instructor edit screen and can be regenerated at any time.
* Fix: buying a membership for someone else now completes the card confirmation step. Previously the gift checkout showed success without ever charging the card — the subscription sat unpaid on Stripe until it auto-expired ~23 hours later and the recipient received a confusing "membership cancelled" email.
* Fix: membership purchases no longer count toward revenue (or send a purchase receipt) before the first payment actually clears. The transaction is recorded as pending and completes when Stripe confirms the charge.
* Fix: when an unpaid membership checkout expires, its optimistic purchase transaction is now marked failed (dropping it from revenue reports) and the buyer gets a clear "your purchase didn't complete — you were not charged" email instead of a membership-cancelled notice.
* Fix: monthly recurring classes no longer slip a day for studios west of UTC — the day-of-month now anchors on the block's start date as a local calendar date.
* Fix: setting a class capacity of 0 now works as documented (unlimited) — those sessions show "Open" instead of a permanent "Full" with a Join Waitlist button, including in the calendar, waitlist promotion, and walk-ins.
* Fix: the booking modal loads on pages where Discovery "Book" buttons are the only booking entry point.
* Fix: virtual appointments no longer send confirmation emails with an empty join link — on-demand appointment sessions now copy the location's virtual meeting URL.
* Fix: the "Add to Google Calendar" links in the Client Dashboard now carry proper timezone-anchored start AND end times — no more shifted or zero-length events for non-UTC studios.
* Fix: the Demo Mode admin-bar badge now shows only to studio operators, not to logged-in clients.

= 1.60.0 =
* New: Age Range badge on classes — assign an Age Range (e.g. All Ages, Kids, Teens, Adults 18+) on the class edit screen and it shows as a badge on class cards, the sessions list, the schedule, and the calendar popover, alongside Type and Level. Optional per class; a new "Class: Age Range Name" dynamic tag covers custom single-class layouts.
* New: the Client Dashboard's upcoming list now shows ALL upcoming bookings — the widget's Upcoming Limit becomes the fold, and anything past it sits behind a "Show all N upcoming classes" button instead of being cut off.
* Fix: joining a waitlist no longer fails with "Please enter a phone number to book" for guests (and logged-in members with no phone on file). The waitlist form never had a phone field; the phone requirement now applies only to requests that can actually create a booking.

= 1.59.1 =
* Fix: sandbox/demo activity no longer reaches the cross-plugin activity stream — while Demo Mode is on, and for any demo-flagged booking, Booking suppresses `gather_grove/activity` emissions, so a live Communities feed on the same site never shows demo bookings as real member activity.

= 1.59.0 =
* New: Access passes — a membership can be flagged "Access pass: grants booking access only". Flagged memberships gate classes (via each class's "Membership Required" setting) without ever acting as a payment option, and they're hidden from the Package Shop. Built for partner-program or location access where staff approve participants individually and billing happens outside per-class payments.
* New: Blocked visitors can request access right on the booking form — when a required membership is approval-gated, the "Membership Required" panel now offers a Request Access form (note + phone). Staff see the request in Membership Requests (and by email) as usual; the panel shows a "request received" state on return visits.
* New: Approving a request for a FREE membership now grants it immediately — the member gets a "you're approved, start booking" email instead of being asked to complete a $0 checkout. Paid memberships keep the approve-then-purchase flow.

= 1.58.1 =
* Fix: promo codes on memberships with long package names no longer fail at checkout with "Invalid string: ...; must be at most 40 characters" — the Stripe coupon label is now clamped to Stripe's limit.
* Fix: memberships can no longer be bought as a one-time class pack from the booking form's "Buy a Package" list. That path charged a single payment (no recurring subscription) and created the membership with a finite session count that wrongly "exhausted". Memberships now purchase only through the memberships page as proper subscriptions.
* Fix: an expired membership record no longer blocks the client from re-subscribing to the same membership.
* New: a daily reconciliation sweep compares every membership against its live Stripe subscription and repairs drift from missed webhooks — a lapsed subscription releases the member's future bookings, dunning pauses booking access, and a paid-up subscription restores it.

= 1.58.0 =
* New: promo codes can now apply to every membership renewal, not just the first payment. Check "Apply the discount to every renewal" on a membership-scoped code to give a client an agreed ongoing rate — the discounted price then bills on every monthly renewal.

= 1.57.3 =
* Fix: approval-gated memberships now show a "Complete Purchase" button to a buyer whose request has been approved. Previously the card kept showing "Request Membership" after approval, leaving the buyer no way to check out.

= 1.57.2 =
* Maintenance: updated the bundled Stripe PHP library from 20.3.0 to 20.3.1.
* Maintenance: documented the Google Maps dynamic tags under "External services" in the readme.
* Fix: the Membership Requests admin table now escapes the status badge at output time.
* Fix: first activation no longer logs two "table doesn't exist" database errors under WP_DEBUG (the installer's existence probe for two tables ran SHOW COLUMNS against tables it hadn't created yet).
* Fix: removed a WordPress 6.7+ "translation loading triggered too early" notice on every page load — the admin-shell module registration now runs at init, after translations are allowed to load.

= 1.57.1 =
* Fix: staff approval links in emails (bookings, membership requests, course enrollments) now open a confirmation page and only act when the Approve/Reject button is clicked — previously the decision fired the moment the link was opened, so email security scanners that pre-fetch links could silently reject (or approve) real requests.
* Fix: invoices now work on hosts that store WordPress security keys in the database (such as WP Engine) — generating an invoice's payment link no longer fails, and the public invoice page no longer errors for invoices with a due date.
* Fix: an invoice paid by a bank-debit method (such as ACH) is now marked paid only when the payment actually settles — previously it was marked paid at submission and stayed paid even if the debit later failed. Failed bank debits are logged for staff follow-up. If you enable bank-debit payment methods, make sure your Stripe webhook endpoint also subscribes to the checkout.session.async_payment_succeeded and checkout.session.async_payment_failed events.

= 1.57.0 =
* New: the Class Discovery widget's card images are now fully styleable, matching the Class Card widget — a Style → Featured Image panel adds Image Source (Featured / Flyer / Auto), Aspect Ratio (presets or custom height), Size, Image Padding (full-bleed or inset), Object Fit, Image Focus, Border Radius, and Box Shadow, all responsive. Image alignment (for images sized under 100%) lives in the Layout panel. Existing Image Source / Image Size selections carry over automatically.

= 1.56.5 =
* Fix: completing 3D Secure on a paid course enrollment now properly finishes the form (previously the button kept spinning, the form stayed open inviting a duplicate submit, and the booking popup never closed).
* Fix: an interrupted card confirmation (network drop mid-3DS, blocked popup) no longer freezes the checkout — every payment step now recovers with a clear message and a re-enabled button, including each member of a family booking.
* Fix: the package/membership purchase window no longer dead-ends with "Purchase failed" when it's been left open long enough for the security token to expire — it now offers the same sign-in-and-continue flow as the booking form.
* Fix: when a class fills up (or requires approval) during 3D Secure authentication of a pack purchase, the buyer now sees the true outcome (waitlisted / awaiting approval) instead of a failure message on a successfully charged card.

= 1.56.4 =
* Fix: a limited membership that used up its monthly allowance now unlocks automatically when the next month's payment succeeds — previously the member kept paying but stayed blocked with "payment required" until staff intervened.
* Fix: a membership whose renewal payment fails now moves to "past due" and loses booking access until the payment recovers (or Stripe cancels the subscription) — previously a failed card kept full access through the entire retry window.
* Fix: membership payment-failure and cancellation emails now reach the member's actual account — previously they could go to the wrong person or nowhere.
* Fix: renewal processing is now idempotent per invoice — a redelivered Stripe event can no longer double-count renewal revenue or re-fill a partially-used allowance.
* Fix: cancelling a membership "at period end" no longer force-resets its status to active (which could resurrect a past-due or exhausted membership into full access).

= 1.56.3 =
* Fix: double-submitting a booking (double-click, two tabs) can no longer create duplicate bookings — the duplicate check now runs inside the same database lock that guards the seat count, so simultaneous submits are serialized and the second one is refused.
* Fix: a Stripe webhook arriving at the same instant as the browser's own payment confirmation can no longer record the booking's revenue twice — the revenue write is now guarded by a per-booking database lock.
* Verified under genuinely parallel load: 8 simultaneous buyers fighting for 1 seat produce exactly 1 confirmed booking (rest waitlisted); 8 simultaneous group bookings against 3 seats seat exactly one group; 8 double-submits produce exactly 1 booking; 8 concurrent revenue writes produce exactly 1 transaction row.

= 1.56.2 =

= 1.56.1 =
* Security: the unauthenticated failed-payment cleanup endpoint now requires the booking's unguessable uuid — numeric booking ids can no longer be enumerated to view or cancel other people's in-flight bookings (completes the 1.52.1 mitigation).
* Fix: bookings on "Requires Approval" classes now honestly report "awaiting studio approval" instead of "Booking confirmed" — on the booking form, in the API response, and in email timing (the confirmation email correctly waits for approval on the card-paid fast path too).
* Fix: the admin Book-a-Client modal no longer silently adds the client to the waitlist when the session is full — it now shows a clear "session is full" error and nothing else happens.
* Fix: gift and family bookings now apply the phone requirement to the buyer, so every purchase has a reachable contact on file.
* Fix: when a package purchase is auto-refunded after a fulfillment failure, the sale is now correctly removed from revenue reports.
* Dev: payment verification and refund paths are now fully testable without Stripe (injectable verifier client; refunds routed through the swappable gateway registry) — with new regression tests locking the payment-verification rules, refund revenue reconciliation, and the members-only enrollment gate.

= 1.56.0 =
* New: Me + Guests — group bookings can now name every seat. Choosing 2+ seats reveals a name (and optional email) field per guest; guests appear on the class roster by name, can be checked in individually, and their seat can be cancelled on its own (freeing exactly one seat) without touching the rest of the group. Still one booking, one charge.
* New: Guests with an email receive their own "you've been booked" notice — including a secure one-click waiver signing link when the class requires a waiver they haven't signed. Email-less guests are flagged "waiver pending" for collection at the door.
* New: Guest attendees are lightweight, login-less records attached to the buyer — they never clutter the household picker, and repeat guests are recognised by name on future bookings.
* Improved: Cancelling a group booking now also clears its named guest seats; approving or rejecting an approval-gated group handles the whole party as one unit.
* Fix: Rejecting a pending multi-seat group booking now releases ALL of its seats (previously only one of N was freed).
* Fix: Guest seats display as "Included — guest seat on booking #N" with a Guest payment badge instead of a misleading "$0.00" / "Paid".

= 1.55.0 =
* New: "Requires Approval" now applies to enrollment courses too. Approval-gated enrollments land in "Pending" (seats held, payment charged as usual) instead of enrolling immediately; the class instructor and studio staff receive an email with one-click Approve / Reject links, and pending enrollments can also be approved or rejected from the Enrollments admin page.
* New: Approving an enrollment activates it, confirms every session, and sends the purchase receipt and confirmations. Rejecting cancels it, releases the seats, automatically issues a full refund regardless of the class refund policy, and emails the client the decision (with your optional note).
* New: Buyers get an immediate "request received" email for approval-gated enrollment purchases, so a charged card is never met with silence.
* Fix: Admin manual bookings, instructor walk-ins, and comps now bypass the "Requires Approval" gate as the class editor always promised — previously they could land in Pending Approval.
* Fix: The Bookings page approve/reject actions now refuse to act on individual course-enrollment sessions; enrollments are approved or rejected as a whole from the Enrollments page.

= 1.54.2 =
* New: the pre-class-lobby and instructor-gating seam for Gather Grove Communities (payload P3 of the ecosystem's Communities plan). Sibling plugins can now ask Booking — through a small public PHP facade, never Booking's tables — which sessions start soon without a lobby, record or clear the community space bound to a session, read a session's confirmed roster (as WordPress user ids, with the lead instructor), and check whether a member has ever attended a given instructor's class. Purely additive; no behavior changes for existing features.

= 1.54.1 =
* New: Booking now announces freshly-created member records — the `member.created` activity event plus a `gather_grove/member_created` hook — so sibling Gather Grove plugins (Communities) can run welcome flows and provision member profiles. Import, migration, and backfill paths deliberately stay silent so existing members never receive replayed welcome messages.

= 1.54.0 =
* New: The instructor portal roster now shows each attendee's email and phone (tap-to-email / tap-to-call), so instructors can reach a student about changes. Toggleable per widget via "Show Client Contact Info".
* New: "Message Class" on the instructor portal — instructors can email every confirmed participant of an upcoming session (subject + message, optional send-me-a-copy), using the same send path as the admin Sessions page action. Toggleable per widget.
* New: The instructor portal schedule filter gains "Past 30 days" / "Past 90 days" presets (and custom ranges may now start in the past), so instructors can review who signed up for and attended previous classes. Past sessions show a "Past" badge; attendance can still be corrected on them, while Cancel Class, walk-ins, and Message Class only appear for classes that haven't run.
* Fixed: A class earlier today no longer disappears from the instructor portal once it's marked completed.
* Fixed: The admin Sessions "Message Participants" action's instructor authorization checked the instructor post's author instead of the linked instructor account, denying legitimate instructors; it now uses the same session-assignment check as portal check-in (lead, substitute, or co-instructor).
* Fixed: Sent participant messages are now actually recorded in the notifications log — the log insert didn't match the log table's columns, so it had been silently skipped.
* Fixed: The walk-in form's confirmation/error message was invisible (shown with a style the stylesheet immediately overrode); it displays now.

= 1.53.0 =
* Improved: The Payment column across the admin (Bookings list + detail, Attendance roster, client profile bookings tab, Dashboard recent bookings) now shows how a booking was covered — "Pack", "Membership", or "Credit" — instead of a misleading "Free" when the client redeemed a prepaid class pack, membership, or session credit. "Free" now only ever means nothing was owed, and settled card charges read "Paid" everywhere.
* Fixed: The Bookings list's Payment / Status / Type badges render as colored pills again — an output filter was stripping their CSS class, leaving plain text.

= 1.52.2 =
* Fixed: The attendance action links (Mark Attended / Mark No-Show / Mark Late Cancel), the Remove buttons, and other JavaScript-driven controls on the admin pages did nothing when clicked. The 1.49.x escape-on-output pass inadvertently stripped the data-* attributes those controls are wired through (WordPress only preserves data-* when an element's allow-list entry declares it explicitly); the allow-list now declares data-* and the common aria-* attributes on all form controls and inline SVG.

= 1.52.1 =
* Security: The booking and appointment checkout endpoints now verify a card payment directly with Stripe before marking a booking paid. Previously a payment-confirmation id supplied in the request was trusted as-is; the server now confirms with Stripe that the payment actually succeeded, covered the full amount, is in the right currency, belongs to the booking, and hasn't already been used — rejecting anything that doesn't check out and releasing the held seat. Normal card payments (which settle through the Stripe webhook) are unaffected.
* Fixed: Enrollment courses with a "Membership Required" restriction now enforce it — previously a members-only course accepted and charged any enrollee because the membership check was only applied to drop-in bookings, not course enrollment. Members with a qualifying membership enroll as before; admin-comped enrollments are unaffected.
* Fixed: Refunding (or cancelling with a refund) a course enrollment now correctly reduces reported revenue. Previously the original enrollment charge stayed counted in the Dashboard and Reports totals after a refund, overstating revenue. Full refunds clear the charge; partial/prorated refunds keep only the retained portion as revenue.
* Security: Tightened the unauthenticated booking-cleanup endpoint so it only applies to bookings whose payment never completed. Previously a signed-out visitor could view or cancel any already-completed free/package booking by guessing its id; now only genuinely unpaid, in-progress bookings are eligible for that guest cleanup path.

= 1.52.0 =
* New: When an enrollment course's registration window has closed, the calendar popover and the schedule card/list views now show a disabled "Enrollment closed" state instead of an Enroll button — no more tapping Enroll only to dead-end on "Enrollment for this course has closed" inside the panel.
* Fixed: The Invoices admin page showed the "No invoices yet" welcome screen forever, even after real invoices existed — the existence check used a query form that can never see the plugin's custom invoice statuses. It now reads through the same query the invoice table uses.

= 1.51.0 =
* New: Admins can now add a client to a class that already happened. The Book-a-Client modal gains a "Show past sessions (last 90 days)" toggle, and a past session's roster (Attendance page) now shows the "+ Add Client" button too. Backfilled clients are marked ATTENDED on the roster for teacher verification, the waiver is flagged for collection if unsigned, and no booking-confirmation email is sent for a class that already ran.
* New: Two payment methods added to the admin Book-a-Client modal — "Studio Credit Balance" (debits the client's balance atomically; insufficient balance blocks the booking) and "Book + Send Invoice" (books immediately as payment-pending and emails the client an invoice with a Stripe payment link — the "attended but hasn't paid" path). Both prefill the class drop-in price and are adjustable.
* Fixed: Admin manual bookings no longer dead-end when the client hasn't signed the waiver — the booking goes through flagged "Waiver pending (admin booking)" in its notes, matching the instructor walk-in contract.
* Fixed: Instructor walk-in invoices were keyed to the wrong account when the client's internal id didn't match their WordPress user id — the invoice (and its email) could go to an unrelated user. Recipients are now resolved through the client's owning user account.
* Fixed: The client detail page's Invoices tab crashed ("critical error" in the Created column) as soon as a client had an invoice — a date-formatting type error, now corrected.
* Changed: "Message All Participants" paces its sends (150ms apart) so a full roster's blast doesn't trip the email provider's burst limits and silently drop messages.

= 1.50.0 =
* Fixed: Paying for a class with a class-pack credit no longer dead-ends on "Please sign the required waiver(s)" with no waiver shown — the waiver now appears inline for review and signing, and the booking completes automatically after.
* Fixed: Buying a pack and booking in one step used to charge the card, cancel the new pack, and auto-refund when a waiver was unsigned — all while showing no waiver. The waiver check now runs BEFORE the charge, the signing form renders inline, and the purchase replays untouched after signing.
* Fixed: Booking several family members at once no longer punts a member with an unsigned waiver to "book them on their own" — each member's waiver is presented in turn (titled with their name), the guardian signs on their behalf, and those members are booked automatically after.
* New: Enrollment courses now require the studio/class waiver like every other registration path. The check runs before any charge; the signing form renders inside the enrollment panel and the purchase resumes after signing. Guests sign via their checkout email, and household enrollments collect the ENROLLEE's signature.
* Fixed: Booking a class as a gift for someone else could loop the waiver form forever (the required signature belongs to the recipient, who isn't present). Gift bookings now go through and are flagged "Waiver pending (gifted booking)" in the booking notes, matching the instructor walk-in follow-up contract.
* Fixed: The enrollment panel's success message ("You're enrolled!") was hidden together with the form on completion; it now stays visible.

= 1.49.0 =
* Changed: WordPress.org review compliance pass. Every remaining short-prefix ("gg") runtime identifier renamed to the full gather_grove / gatherGrove prefix: transient keys (rate limiters, report caches, admin notice hand-offs, calendar fragment cache, promo grants), the two custom cron schedule names, six script handles, and the JavaScript settings objects. Identifiers that carry studio data (post types, taxonomies, database tables, post meta, capabilities, roles) keep their existing names. One-shot migrations reschedule cron events off the old schedule names and rename the queued-gift-welcome user meta in place.
* Changed: The gift-card redeem landing page loads its redeem button script from a registered file instead of an inline script tag; two admin screens (Membership Requests, the class registration-close panel) attach their scripts via wp_add_inline_script instead of raw script tags.
* Fixed: Guest gift-card purchases no longer switch the request's current user to the buyer's account while processing — the buyer is resolved directly instead.
* Changed: Hardened query construction (explicit prepare placeholders) in the demo-data purge, gift-card updates, and automation queue; array inputs on the Instructor and Service editors are sanitized field-by-field at extraction.

= 1.48.0 =
* New: Instructors (and front-desk staff) can now sign a student into a class from the Instructor Dashboard walk-in form during or after the class — even once it has started or ended earlier that day, and even after online registration has closed. The walk-in still requires an email and automatically sends the drop-in invoice, so the student gets billed. Sessions from a previous day are not eligible.
* New: A class earlier today now stays visible on the Instructor Dashboard (instead of dropping off once it starts) so its roster is reachable for check-in and walk-ins.
* Changed: Walk-ins no longer blocked by an unsigned waiver. The booking goes through and is flagged "Waiver pending" in its notes so the studio can collect the waiver at the next visit. Online self-registration still requires the waiver as before.

= 1.47.7 =
* Fixed: Promo codes now also apply when gifting a class pack to someone else (the last checkout flow that ignored them). The discount reduces the gifter's charge and the redemption is recorded against the gifter, matching every other purchase path.

= 1.47.6 =
* Fixed: Promo codes now also apply when you gift a membership to someone else. The discount reduces the gifter's first payment (via a one-time Stripe coupon) and the redemption is recorded against the gifter, consistent with buying a membership for yourself.

= 1.47.5 =
* Fixed: Promo codes now actually discount membership purchases. A promo entered on a membership was validated but never reduced the charge — the customer was billed full price — and on the normal (card-confirmation) checkout it wasn't even recorded as redeemed. Memberships now apply the discount to the first payment (as a one-time Stripe coupon) and record the redemption, on both the instant and card-confirmation checkout paths. Renewals continue at the regular price.

= 1.47.4 =
* Fixed: A new membership now activates as soon as its first payment clears. Previously, when the card needed confirmation, the membership could linger as "pending" on the member's dashboard even though it was paid and usable.
* Fixed: The first membership charge is no longer double-counted in revenue. A duplicate "renewal" transaction was being recorded alongside the initial payment; the renewal transaction is now logged only for genuine renewals.
* Fixed: A background waitlist-cleanup task (expiring stale waitlist offers) was erroring after an internal method rename. It now runs correctly.

= 1.47.3 =
* Fixed: A membership or class-pack purchase that required card confirmation (the normal path for subscriptions, and any card that prompts 3-D Secure) could show "Purchase failed" even though the card was charged and the purchase actually went through. The checkout now correctly reports success once the card is confirmed.

= 1.47.2 =
* Internal: Added diagnostic logging when a class pass is refused at checkout, capturing just enough sign-in context (no secrets) to distinguish a genuinely signed-out visitor from one whose session quietly lapsed — so the rare "can't use that pass" report can be pinpointed if it recurs.

= 1.47.1 =
* Changed: Buying a membership now prompts you to log in or create an account first, instead of showing a card form that couldn't complete. A membership is an ongoing subscription tied to your account, so an account is required. The prompt is an in-page login/register pop-up — you don't leave the page, and once you're signed in the membership checkout reopens automatically right where you left off.
* Fixed: If your login session quietly expired while a class page was left open, paying with your own class pass could fail with a confusing "You can't use that pass for this booking." message. It now recognises the lapsed session, prompts you to log back in, and reopens the booking so your pass applies correctly.

= 1.47.0 =
* Fixed: The Schedule widget's "Show Past Sessions" toggle had no effect in the calendar view — past sessions in the current month always showed. The calendar now hides past sessions when the toggle is off (and the card/list views keep it consistent across filter/pagination refreshes).
* Added: Registration close windows. Set when booking/enrollment closes for a class — a studio-wide default (Settings → Bookings: "Registration closes N minutes/hours/days before start"), with per-class overrides on the class edit screen (inherit the default, a custom relative cutoff, or a fixed date & time). For drop-in and open-enrollment classes the cutoff applies before each session; for cohort courses it applies before the course starts. Open Enrollment still controls whether people can join after a course begins — it sets what the cutoff is measured against, it doesn't remove the deadline. Front-desk/admin walk-in registration can still be taken after the cutoff.

= 1.46.0 =
* Fixed: Membership sign-ups could complete without ever charging the card on newer Stripe API versions. Stripe (API 2025-03+) relocated several subscription/invoice fields — the first-payment client secret, the PaymentIntent status, the billing-period end, and the invoice's subscription link. Membership purchase, gifting, activation, next-billing date, and renewal handling now read the new locations (with fallbacks to the old ones), so the card is confirmed and the subscription activates instead of being stranded "incomplete".
* Added: Outbound email is now rate-limited so bulk sends (or a booking that fires several notifications at once) don't trip an email provider's per-second send limit and silently drop mail. Sends spread automatically under load and any failed send is retried with backoff. Tune with the "emails per minute" setting (or the `gather_grove_mail_rate_per_minute` filter).

= 1.45.0 =

= 1.44.1 =

= 1.44.0 =

= 1.43.1 =
* Fixed: Membership purchases could attach the Stripe customer to the wrong person (when a client's internal ID coincided with another user's account ID). The paying customer is now always tied to the correct account.

= 1.43.0 =
* Fixed: Unlimited memberships were wrongly marked "exhausted" after the first class booked with them, so the member then saw "payment required" on every later booking. Unlimited memberships now stay active as intended.
* Changed: Bookings paid for with a membership or class pack now show "Membership" / "Class Pack" (with the package name) in the admin Amount column instead of a misleading "$0.00".

= 1.42.0 =
* Added: Staff Notification Recipients setting (Settings → Notifications) — route booking-approval and membership-request notifications to a shared inbox instead of every admin and manager. Leave blank to keep notifying all staff; the class instructor is always notified of booking approvals either way.

= 1.41.1 =
* Fixed: Calendar list view was hard to read — each event row was filled with the full category colour behind dark text (worst on mobile, where list view is the default). List rows now use the calendar's normal neutral row with a colour-coded dot and readable text; month and week views still show events in their category colours.

= 1.41.0 =
* Added: Package Shop "Specific Packages" filter — show exactly the package(s) you pick (in the order you pick them), e.g. a landing page for a single bundle.
* Fixed: Package Shop class/category filtering now respects each package's Applies-to / Except targeting. Previously a package excluded from a category could still appear, and packages with no targeting showed under every filter; filtering now uses the same rules as the booking flow.

= 1.40.0 =

= 1.39.1 =
* Fixed: Guests booking a class that requires a waiver could not complete the booking — signing the waiver returned "Sorry, you are not allowed to do that." Guest waiver signing now works: the guest signs inline with the email they entered at checkout, and the booking proceeds. Affected any studio using a studio-wide or per-class waiver together with guest (logged-out) booking.

= 1.39.0 =
* Fixed: The Dashboard "This Week's Revenue" tile ignored course enrollment income — a paid enrollment showed as $0 because the payment is recorded once on the enrollment rather than per session. The tile now reads the same revenue ledger as the Reports page, so drop-ins, packages, memberships, enrollments, and gift cards all count.
* Changed: Enrollment session rows in the admin booking lists (Bookings, Dashboard, and client profile) no longer show a misleading "$0.00" — each row now reads "Included" with the enrollment's total price and session count, so a paid course is clearly paid.

= 1.38.1 =
* Fixed: On a course enrollment, the "+ Add a family member" button did nothing — clicking it didn't reveal the add-member form (it worked on drop-in class bookings). Enrolling a parent can now add and pick a household member to enroll.

= 1.38.0 =
* Added: Instructor intro video — paste a YouTube or Vimeo link in the new "Intro Video URL" field on an instructor to show an intro video on their profile. A "Show Intro Video" toggle on the Instructor Profile widget controls whether it appears.
* Fixed: Package and membership purchases failed when two Package Shop widgets were on the same page (e.g. separate Memberships and Class Packs sections) — the Stripe card field didn't appear, so nothing could be bought. The purchase modal is now shared once per page instead of duplicated per widget.
* Fixed: The Class Schedule widget's Calendar view now respects the Pre-set Default filters (Default Category, Type, Level, Instructor, Location). Previously these were ignored in Calendar view, so a calendar set to a default filter still showed all sessions.

= 1.37.0 =
* Fixed: When a parent books a dependent's appointment, the parent now receives a copy of the confirmation (the member still gets theirs too).
* Fixed: The "Who is this for?" family member picker (on the booking form, appointment widget, and enrollment panel) showed its options as raw HTML text instead of selectable radio buttons.

= 1.36.0 =

= 1.35.0 =
* Added: The "Require Phone Number" setting now also applies to course enrollments, appointment bookings, and package/membership purchases (it already covered registration and class bookings). Buying a gift for someone else never requires the recipient's phone, and group bookings only require the booker's.

= 1.34.0 =
* Added: "Require Phone Number" setting (Settings → Booking), on by default. When enabled, participants must provide a phone number when they register and when they book — useful for reaching everyone about class cancellations. Turn it off if your studio doesn't collect phone numbers.

= 1.33.1 =
* Fixed: Filtering the Class Schedule, Sessions List, calendar, and Package Shop by a parent Category now also includes classes filed under that category's sub-categories. Previously, selecting a top-level category (e.g. "Ceramics") missed classes tagged only to its children (e.g. "Wheel Throwing", "Hand-Building"). Applies to any hierarchical taxonomy.

= 1.33.0 =

= 1.32.2 =
* Fixed: Internal code-quality cleanups so the free build passes WordPress.org's Plugin Check more cleanly. No functional changes.

= 1.32.1 =
* Fixed: The Plugin URI and Author URI headers are now distinct (a WordPress.org directory requirement). No code changes.

= 1.32.0 =
* Changed: WordPress.org compliance pass. Globally-scoped, non-data-bearing identifiers (AJAX/admin-post actions, nonce actions, transients, and options) moved from the short `gg_` prefix to the full `gather_grove_` prefix. A one-time, value-preserving upgrade migration carries existing options and onboarding settings over to the new names, so upgrades are seamless. Data-bearing identifiers (post types, taxonomies, tables, meta, capabilities) are intentionally unchanged.
* Changed: Stripe PHP SDK updated to 20.3.0.
* Fixed: All widget and admin markup is now escaped at the point of output; array form inputs are sanitized when read; the client email-lookup endpoint no longer returns the account holder's name; the last inline admin script moved into the enqueued admin JavaScript file. No user-facing feature or behavior changes.

= 1.31.0 =
* Fixed: The Instructor Dashboard widget's editor preview ("My Profile tab" state) now matches the live profile form — including the rich-text bio, the Specialties and External Links repeaters with their "+ Add" buttons, the notification cadence selector, and the per-session "Cancel Class" button. The live dashboard was already complete; this just brings the in-editor styling preview up to date.
* Changed: External-link pills on the Instructor Profile widget are now clearly distinguishable as links — a subtle border, a trailing arrow, and a clear hover fill — so they no longer blend in with specialty tags. Still fully customizable via the widget's Links style controls.

= 1.30.0 =
* Added: Promo codes can now target by class Level and Type, not just Category and specific classes — bringing them to parity with memberships and packages. The promo "Applies to" / "Except" picker now offers Categories, Levels, and Types (with the same nested parent → child display and cascade). Existing promo codes are unaffected until you edit and re-save them.

= 1.29.0 =
* Changed: Category and Type targeting in memberships, packages, and promo codes is now hierarchical — selecting a parent category (or type) automatically covers everything nested under it (e.g. picking "Yoga" now also covers "Vinyasa", "Restorative", and any other Yoga sub-category, including ones you add later). "All Yoga except Vinyasa" still works exactly as before. Heads-up: any existing membership, package, or promo code that targeted a parent category/type will now also cover its child categories.
* Changed: The "Applies to" and "Except" pickers (memberships, packages, and promo codes) are now collapsible and show categories as a nested tree — child categories are indented under their parent, parent categories are labelled "(includes everything under it)", and each section shows a selected-count summary when collapsed. Much easier to scan and set on studios with lots of categories.

= 1.28.0 =
* Added: Instructors can now cancel their own classes right from the Instructor Dashboard. Each upcoming session has a "Cancel Class" button (with a confirmation prompt); cancelling notifies booked clients automatically, the same as an admin cancellation. Instructors can only cancel sessions they teach (lead, substitute, or co-instructor).
* Added: "Show Cancelled Classes" toggle on the Instructor Dashboard widget (off by default) — when on, the instructor's cancelled sessions appear alongside their upcoming schedule with a Cancelled badge.
* Fixed: The Schedule widget's "Show cancelled sessions" setting had no effect in Calendar view — cancelled sessions were shown in card/list views but never on the calendar. The calendar now honours the setting and displays cancelled sessions with a Cancelled badge, matching the other views.

= 1.27.3 =
* Added: When you book a class as a gift for someone else, the buyer now receives their own purchase receipt (with the amount charged and who it's for) in addition to the recipient getting their booking confirmation. Previously only the recipient was emailed, so the person who paid heard nothing.
* Changed: The booking form's "Booking for someone else?" toggle is now labeled "Send this as a gift to someone else," with a hint clarifying that the booking and its confirmation go to that person — and steering anyone who's attending themselves and bringing guests to the number-of-seats option instead. Reduces the mix-up where the buyer ended up as a guest on the recipient's account.

= 1.27.2 =
* Fixed: The admin Dashboard "This Week's Revenue" stat showed $0.00 even when paid card bookings existed. It was filtering on a booking status that card payments never use; it now counts every settled booking, so revenue reflects reality.
* Fixed: Card (Stripe) drop-in bookings were not recorded in the financial ledger, so their revenue was missing from the Reports page totals (only split-payment bookings were logged). Each card booking now writes a booking-payment transaction when it settles — via the webhook or the inline confirmation path — so Reports reconcile. Idempotent: no double-counting on webhook retries.

= 1.27.1 =
* Fixed: Card-paid bookings (and any payment fulfilled in the background — drop-ins, enrollments, packages) could be charged successfully but never confirmed: the seat wasn't held, no confirmation email was sent, and the booking was later auto-cancelled as "payment did not complete" even though the card had been charged. The background fulfillment step that runs after Stripe confirms a payment was not registered in the context where it actually runs, so it failed every time. It is now registered correctly and runs reliably. Studios on the previous build should reconcile any charged-but-cancelled bookings in Stripe.

= 1.27.0 =
* Added: Instructors who also take classes can now hop between their Instructor Portal and their member account in one click. The Instructor Dashboard shows a "My Account" link, and the Member Dashboard shows an "Instructor Portal" link (only to members who are also instructors). Both have show/hide toggles, custom link text, and full style controls.

= 1.26.0 =
* Added: Memberships and packages can now be targeted by class Level and Type (e.g. "all Assisted-level classes"), not just Category or a hand-picked list — and they automatically cover matching classes you add later. Found under Class Restrictions → Applies to / Except.
* Added: The Package Shop "Valid for" pills for Levels, Types and "All classes" now have their own Background and Text Color style controls (Named Tags), so you can brand them independently of the colour-coded class pills.
* Fixed: Category "Valid for" pills showed a washed-out neutral colour instead of the category's own colour, which looked inconsistent beside the class pills. Category pills now use their category colour, and the remaining neutral pills track your brand palette.

= 1.25.0 =
* Fixed: Rich-text Descriptions and the Instructor Biography (added in 1.24.0) lost their formatting and reverted to plain text when you clicked Update/Save. The fields were registered with a sanitizer that stripped all HTML on every save; they now keep safe formatting. Affects Instructor bio and Package, Class, Room, and Service descriptions.
* Added: The Biography field on the Instructor Dashboard "My Profile" tab is now a rich-text editor (bold, italics, bulleted/numbered lists, links) — matching the admin editor and the rest of the 1.24.0 rich-text sweep. Instructors no longer need admin access to format their bio, and the formatting shows on their public profile.

= 1.24.1 =
* Fixed: Paid course-enrollment checkout failed with a generic error and never charged the card (a settings class was imported from the wrong namespace, throwing before the Stripe charge). Affected every paid enrollment course; drop-in, package, membership, and gift-card checkouts were unaffected.

= 1.24.0 =
* Added: The Description fields for Packages/Memberships, Classes, Locations, Rooms, Services, and the Instructor Biography are now rich-text editors (bold, lists, links, etc.) instead of plain text boxes — and the formatting shows on the matching public surfaces (Package Shop, class cards, instructor profile, description dynamic tags). Word-limited preview tags still show clean plain text.

= 1.23.0 =
* Added: Instructors can now manage their own external links and specialties right from the "My Profile" tab of the Instructor Dashboard — add/remove links (label + URL) and as many specialties as they like, no admin access needed. (Builds on the admin editor + profile pills from 1.22.0.)

= 1.22.0 =
* Added: Instructors can now have a list of external links (personal website, social profiles, portfolio, etc.) — repeatable Label + URL rows in the Instructor editor, shown as elegant clickable pills on the Instructor Profile widget (with their own style controls). Specialties are no longer limited to 3 — add as many as you like. (Instructor self-service editing from their dashboard comes next.)

= 1.21.1 =
* Added: Calendar List View now has a "Row Background" control that styles event rows on every device — the way to color the list view on mobile, where rows have no hover state. Plus a hover transition for desktop.
* Fixed: Calendar list-view event titles are now properly colorable. Because the title is a link, the "Title Color" control was being overridden by your theme's link color (and stuck on the tapped color on mobile); it now wins in every state, and there's a new "Title Hover Color" control for desktop.
* Fixed: The list view's "Row Hover Background" control now applies reliably (it could previously be overridden by FullCalendar's own styles) and is marked desktop-only.

= 1.21.0 =
* Added: Duplicate Class — a "Duplicate" link on the Classes list clones a class to a draft. A small dialog lets you set the new title and choose whether to also copy the schedule and images; everything else (pricing, booking rules, targeting, description, categories) is copied. The copy is a draft, so no sessions are created until you review and publish.

= 1.20.0 =
* Changed: Promo codes now use the same flexible class targeting as packages and memberships — an "Applies to" set (categories and/or specific classes) plus an optional "Except" set (e.g. "all Yoga except the Advanced Workshop"). Categories and specific classes combine with OR.
* Changed: The promo editor's old "restrict by taxonomy" picker is replaced by the unified targeting UI. Existing promo codes keep working exactly as before until you edit and re-save them.

= 1.19.0 =

= 1.18.0 =
* Fixed: A package set to BOTH a category and specific classes could stop showing at checkout for classes in the category that weren't in the specific list (the two were wrongly required together). They now match on either.
* Fixed: Membership cards in the Package Shop showed a wrong class count (e.g. "10 classes"); they now show the membership's per-period allowance (e.g. "2 classes per month") or "Unlimited classes".
* Fixed: Packages restricted to a category showed "All classes"; they now name the category ("Yoga"), and show "except …" when exclusions are set.

= 1.17.2 =
* Fixed: Household dependents showed as "Client #N" / "Guest" in the admin Dashboard's Recent Bookings, the Attendance check-in roster and waitlist, and the instructor portal's class roster. All now show the dependent's name.

= 1.17.1 =
* Added: The Book Client modal's client search now finds household members and dependents (including children with no login of their own), not just account holders — so you can book or comp a dependent directly. Brand-new members without a client record yet are still bookable too.
* Fixed: Bookings made for a household dependent showed as "Guest" in the Bookings list and detail view. They now show the dependent's name.

= 1.17.0 =

= 1.16.1 =
* Fixed: Creating an enrollment (including the new Comp Enrollment action, and the public enrollment checkout) failed with "Enrollment fan-out failed" on courses with more than one session. Each session booking now gets its own unique handle so the whole course books correctly.
* Added: The Comp Enrollment client picker now finds household dependents (e.g. children with no login of their own), not just account holders — so you can comp an enrollment for a dependent.

= 1.16.0 =

= 1.15.0 =

= 1.14.1 =

= 1.14.0 =
* Added: "Show editor hint" toggle for a true WYSIWYG editing experience. When styling a widget in the Elementor editor, the small "Editor preview:" caption that named the preview state could push small widgets around (for example a Login button set to Icon Only inside a heading), so the editor no longer matched the live page. The caption is now off by default — the editor canvas matches the live look out of the box. To bring the labels back, turn on Content → Editor Preview → Show editor hint. No effect on the live page.

= 1.13.0 =
* Added: Split payment — when a client's account balance (for example a redeemed gift card) covers only part of a class price, the booking form now offers "Use $X balance + card ($Y due now)". It applies the balance and charges the card for the remainder in one checkout — solving the "$50 gift card on a $55 class" case. The card is captured before the booking and auto-refunded if the booking can't be completed; 3DS/SCA is supported.
* Added: Refunds of a split-payment booking return the card amount to the card and the balance amount back to the client's account balance, in a single admin action.

= 1.12.0 =
* Added: Redeem a gift card from the new "Gift Card" tab on the Client Dashboard — the value is added to the client's account balance, ready to spend on any class or appointment at checkout. Redeeming always works, even if a studio's plan lapses, so issued cards are never stranded.

= 1.11.13 =
* Fixed: The Gather Grove Login widget in Button + Modal mode now previews the trigger button in the Elementor editor by default (it used to show the inline form), so the Account Button and "Icon Only" settings are visible and styleable as you change them.
* Fixed: An "Icon Only" account button using an uploaded SVG icon no longer renders blank — the SVG is now sized correctly. The account icon also has a sensible default size so label-less buttons are clearly visible.
* Fixed: The "Show Family/Household Picker" toggle on the Booking Form and Appointment widgets now actually hides the picker when turned off (the toggle previously had no effect).
* Fixed: The Login widget in Button + Modal mode now shrinks to fit its button — an icon-only account button in a header no longer reserves a wide empty area beside it.

= 1.11.12 =
* Fixed: A styled "Modal host" Booking Form placed inside a Theme Builder template (e.g. your single Class or Location template) now correctly skins the in-page "Book" popup. Previously the popup opened with the default, unstyled form because it was looking at the wrong page for your styling and settings. The same fix applies to the Appointment booking widget's modal host on Service templates.
* Fixed: In the Elementor editor, the Sessions List widget's "Book" button now updates with your custom Book Button Text while previewing with Sample Data (it already did with Real Data).
* Added: The Schedule, Home, single Class, and single Location starter pages/templates now include a hidden "Modal host" Booking Form, so the in-page booking popup is styled out of the box on a fresh import.

= 1.11.11 =
* Changed (internal): Settings now read and write through the shared Gather Grove core settings layer instead of a Booking-private copy. No change to your settings or behavior — this keeps the Gather Grove plugins consistent and is groundwork for upcoming products in the suite.

= 1.11.10 =

= 1.11.9 =

= 1.11.8 =
* Added: Tiered discount promo codes — a promo code can now apply a bigger discount at higher quantities, e.g. "10% off for 2–3 spots, 20% off for 4 or more". Choose "Tiered (by quantity)" as the discount type and add as many tiers as you like (each with its own minimum quantity and percent or dollar discount). Works on group bookings and family orders.

= 1.11.7 =
* Added: Group-discount promo codes — a promo code can now require a minimum number of spots before it applies (e.g. "10% off when you book 2 or more"). Set "Minimum Quantity" when creating a code; it counts seats in a group booking or members in a family order. Codes with no minimum are unchanged.

= 1.11.6 =

= 1.11.5 =
* Changed: Updated bundled libraries to their latest stable releases — Stripe PHP SDK 20.2.1 and Action Scheduler 4.0.0. Action Scheduler 4.0.0 requires WordPress 6.8, so the plugin's minimum supported WordPress version is now 6.8.
* Security: The logged-in /clients/check-email lookup (used by the gift and household-link flows) is now IP-rate-limited to deter email enumeration. No change for normal use.

= 1.11.4 =

= 1.11.3 =

= 1.11.2 =
* Changed: with no family members yet, the picker now collapses to a single, unobtrusive "+ Add a family member" link — so solo bookers see almost nothing — and reveals the full picker once a member is added.
* Fixed: the "+ Add a family member" button no longer picks up your site's global button style; it now matches the widget's palette.

= 1.11.1 =

= 1.11.0 =
* Changed: the member payment options now update automatically when you pick who the booking is for.
* Security: added a server-side check so a pass or account balance can only be used by the person who owns it (or, for a household member, by the payer who manages them).

= 1.10.4 =
* Fixed: the password-reset, email-verification, and guest-account-welcome emails no longer show the "you booked a class" footer line, which didn't fit those account emails. They now read accurately (e.g. "…because a password reset was requested for your account" / "…because an account was created for you"). If you've set a custom email footer, that still applies everywhere as before.

= 1.10.3 =
* Fixed: the password-reset email now displays as a formatted message instead of showing raw HTML code in the inbox. The previous update fixed the email's content, but on sites using an email-delivery (SMTP) service the message was still sent as plain text. It now carries the correct "this is HTML" header so it renders properly everywhere.

= 1.10.2 =
* Fixed: the password-reset email now sends as a proper, fully-branded HTML message. Previously it could arrive showing raw HTML code, and the "Reset Password" link could loop back to the "enter your email" page instead of letting you choose a new password. Both are resolved.

= 1.10.1 =

The complete version history is maintained in CHANGELOG.md within the plugin.

