Back to BookingWP WooCommerce Appointments
BookingWP WooCommerce Appointments changelog
Release notes for every version we have on record, newest first.
Latest 5.5.1 · 29 Oct 2026
40 versions
Since Apr 2026
2026 40 versions
5.5.1 Latest 29 Oct 2026 9 improved11 fixed
Improved
- Storefront: selection info shows an end date or end time when the appointment spans more than one unit (e.g. Oct 28 - 29, or 08:30 - 13:00).
- Storefront: opening with a date in another month paints that month immediately from bootstrap data (used only for the month it covers).
- Admin panel: location and resource option labels show additional cost suffixes like the storefront (e.g. (+$20)).
- Admin menu: Appointments uses the product a+clock glyph (WordPress 7.2 SVG Icons API; base64 SVG fallback on older WP) instead of dashicons-clock.
- Tests: IcsExporterStaleEntryRegressionLiveTest locks staff DESCRIPTION single n escaping (#1496); php-date-format-escape.test.ts locks storefront wca_format_date PHP escapes (#1495); ResourcesLiveTest covers display assignment is_appointable when the booking location has no location-scoped room; TranslationAvailabilityIndexSyncLiveTest locks original-product availability for translations.
- Wc_appointments_get_product_id_for_availability(); removed WCML/Polylang availability cloning and unused data-store row helpers.
- Index minute masks overlay append-only woocommerce_appointment_get_availability_rules deltas once per staff (get_availability_rules_filter_delta → overlay_rules_on_mask).
- All appointment wc_get_template calls pass $template_path woocommerce-appointments; outdated-template notice also scans that theme folder.
- Build: npm run check:debug-leftovers fails on Cursor agent ingest leftovers and stray debug-*.log files; wired into build / build:no-test (#1500).
Fixed
- Storefront: selection dates honor WordPress date-format escapes (e.g. Portuguese l, j de F, Y shows "2 de Novembro" again) (#1495).
- Storefront: automatic/display resources no longer block add-to-cart at a venue that has no eligible room.
- ICS export: DESCRIPTION staff/resource/add-on line breaks show as real newlines again (no literal n text).
- Admin panel: create footer quotes and order totals include location and resource surcharges again; cost AJAX posts the selected location/resource like availability already did.
- Admin panel: resource dropdown lists only rooms for the selected location (no fallback to the full pool when a venue has none), and create waits for a customer resource pick before quoting cost or checking availability.
- Appointments export: CSV downloads include the column header row again, and CSV no longer double-loads every appointment before writing the file.
- WPML / WCML / Polylang: product availability rules belong to the original product only. Translations use that schedule for slots and the Availability hub (no cloned per-language rules).
- Availability / slots: indexed slot lists honor filter-injected availability rules (same as the dynamic path), so busy windows no longer stay bookable when the index is used.
- Templates: theme overrides default to yourtheme/woocommerce-appointments/ (headers and loaders aligned). yourtheme/woocommerce/ still works via the locate filter.
- Availability hub: product/staff/location search matches accented letters and curly quotes (e.g. étalons, d’…) (#1493).
- Storefront: staff/resource picker no longer calls a leftover local debug endpoint (Chrome no longer prompts for access to other apps on the device) (#1500).
5.5.0 23 Oct 2026 12 improved13 fixed
Improved
- Removed WC_Product_Appointment::get_qty_input_max() and woocommerce_appointments_quantity_input_max. Use get_qty_max() / woocommerce_quantity_input_max for the per-booking quantity ceiling.
- Admin: Capacity is the shared label for product inventory, staff, resources (was Max at once), and availability overrides (was Spots). Minimum and Maximum stay visible for per-booking quantity even when product Capacity is 1.
- Admin panel: Memberships discounts apply for the selected customer on create.
- Admin: What's New leads with a hero and hub CTA, autoplays the hub story (no long sticky scrub), shows three clickable highlight cards, shortens get-started and roadmap, and keeps copy aligned with shipped 5.5 (Capacity, Fixed show/hide, Meet on the product Location tab).
- Storefront quantity max uses get_qty_max(); install no longer adds unused idx_qty_max_lookup.
- Cost calculation no longer returns a sticky prior WC_Appointments_Cost_Calculation::$appointment_cost (each quote recomputes so customer_id filters apply).
- Tests: IcsExporterStaleEntryRegressionLiveTest locks ICS TEXT escaping, ORGANIZER mailto sanitization (incl. CR/LF rejection), SUMMARY comma/semicolon escaping, and blogname specials in X-WR-CALNAME / ORGANIZER CN.
- Tests: GcalSyncOwnershipLiveTest locks staff own-calendar start/chunk/stop/clear success and denies global/cross-staff sync AJAX without mutating progress.
- Tests: CreateAppointmentStatusPriceGateLiveTest locks status allowlist, staff create paid/override with manage_appointment_status, and forced confirmed + ignored override without it.
- Tests: GcalOauthCallbackOwnershipLiveTest locks OAuth callback denial for forged global state and can_manage_gcal_for_staff for staff/manager.
- Tests: StaffContactPiiGateLiveTest locks calendar/SSE contact redaction, SSE staff scope, and get_customer_details soft perms (view/create/relationship).
- Tests: MembershipsPanelCustomerCostLiveTest and MembershipsGetMemberPriceTest lock panel customer_id cost discounts and Memberships get_member_price.
Fixed
- Google Calendar: calendar list and event requests no longer disable TLS certificate verification; debug logs record token presence only, not the bearer value.
- Google Calendar: stop, process-chunk, and clear-progress AJAX use the same staff ownership check as start (staff cannot wipe global or another staff sync).
- Google Calendar: OAuth callback re-checks staff/global ownership after state parse (same rules as the connect AJAX initiator).
- Admin panel: create validates appointment status; staff without Change Appointment Status cannot set paid/etc. or a custom price override (create still succeeds as confirmed with calculated price).
- Admin calendar: staff without Contact Customers no longer receive customer email/phone (or billing contact) on calendar, appointment REST, or SSE; SSE also omits other staff’s appointments.
- Admin panel: get customer details respects staff View Customers / View All / Create Appointments (and redacts email/phone without Contact Customers unless create autofill).
- Emails: admin new-appointment HTML escapes the customer billing name.
- ICS export: TEXT fields escape backslashes; organizer mailto uses a sanitized admin email.
- Admin panel: customer search no longer loads unbounded WooCommerce matches for terms of 3+ characters (caps at 20).
- Admin calendar: view panel no longer shows the creating staff as assigned Staff when the appointment has no staff assignment (creator-only visibility for list/calendar is unchanged).
- Admin panel: create success step shrinks to fit its content instead of keeping the tall filled create-form height.
- Admin panel: selecting a customer on create updates the preview title in place and yields in-flight cost AJAX so billing details are not stuck behind the quote (availability keeps running).
- Admin panel: member cost quote no longer snaps back to full price after customer billing details load.
5.5.0-rc2 20 Oct 2026 3 improved6 fixed
Improved
- Virtual meetings: cart and admin create call WC_Appointments_Location::apply_to_appointment after the appointment object exists (single write site for location + meeting_provider).
- Tests: MeetingsLiveTest covers pending conference apply, static Join fallback, stable GCal requestId, meeting_provider snapshot (incl. cart create path).
- Tests: CartSimulationRequestLiveTest covers PayPal simulate-cart AJAX names (incl. SDK v6 substring), Store API path, and wc_appointments_is_cart_simulation.
Fixed
- Virtual meetings: emails and order summaries use the same Join link as My Account (Meet, else location static Join URL).
- Virtual meetings: pending Google Meet creates no longer mark failed or mint a new room on each sync (stable Calendar requestId).
- Virtual meetings: Video meeting provider is snapshotted onto the appointment when the location is applied at booking, so later product edits do not change Meet create for existing bookings.
- Storefront: PayPal Payments cart simulation (legacy and SDK v6) no longer creates in-cart appointments or holds slots when add-ons change on the product page (#1491).
- Admin panel: create and edit list the same open times as the storefront for the selected room (day chips and month availability).
- Admin panel: changing location or opening create from a calendar day keeps the date; the first open time fills after location and room are ready, and time fields stay disabled while slots load.
5.5.0-rc 17 Oct 2026 2 new13 improved2 fixed
New
- Locations: product Location mode can be Fixed (assign one venue, hide it from cart, checkout, thank-you, and customer emails), matching resource Selection → Automatic (#1488).
- Admin panel: set the customer's timezone when creating an appointment (hidden only when Timezone Display is disabled everywhere; saved on the appointment and customer account when linked) so emails and My Account show their local time (#1490).
Improved
- Admin panel: create uses one Customer card (search + contact/address/timezone); payment method and transaction ID sit under Order.
- Admin calendar: create and edit no longer book unavailable times by default. Screen Options → Allow booking unavailable times restores the confirm-and-proceed override; the date/time notice scrolls into view when out of sight (#1388).
- Availability hub: Locations and Rooms can use Range of days (whole weekdays open or closed), matching day-duration room schedules.
- Locations / resources: WC_Appointments_Location / WC_Appointments_Resource data objects own CPT admin + domain helpers (removed WC_Appointments_Locations / WC_Appointments_Resources); CPT data stores appointments-location / appointments-resource; helpers get_wc_appointments_location() / get_wc_appointments_resource(); WC_Appointments_Resource_Data_Store::get_appointment_ids_in_range().
- Tests: AdminCreateUnavailableGateLiveTest locks default deny vs Screen Options allow for admin create outside open hours.
- Tests: LocationsLiveTest covers Fixed (hidden) store + customer display omit.
- Locations / resources: hub entity lists and Features existence probes use WC_Appointments_Location_Data_Store / Resource_Data_Store::query() / exists() (no ad-hoc get_posts / $wpdb in those callers).
- Appointment data store: clear_product_id() / clear_staff_ids() / clear_customer_id() / mark_reminder_sent() / clear_reminder_meta() for admin cleanup and reminder guards.
- Availability data store: expired-cleanup candidates/mark, get_rows_by_kind_id plus insert/update/delete for Polylang/WCML sync and cron (no direct table SQL in those callers); PHPStan WC_Data_Store stub covers those methods; drop dead is_wp_error on wc_appointments_get_total_available_appointments_for_range (never returns WP_Error).
- Product appointment data store: staff relationship helpers (get_relationship_rows_for_product, insert/replace/update/delete, panel/REST joins); update_staff / remove_staff_from_product reuse them; plus pricing/legacy availability and staff cost meta helpers for Polylang/WCML/admin/REST.
- Appointment data store: update_last_status() for Follow-Up Emails.
- Tests: DataStoreOwnershipGuardTest also owns wc_appointment_relationships, staff cost meta, and _last_status; no registry debt_paths. Live: location/resource data-store meta round-trips.
- Tests: AvailabilityLocationResourceRulesLiveTest locks OR-pool resource Open hours (#1487) and resource days Open on day-duration products.
Fixed
- Product edit: compact number/unit and help-tip layout applies only to appointment fields (Duration, Inventory, Availability Check), so simple and other product types keep WooCommerce select widths and tip placement.
- Availability / slots: Automatic rooms with Open hours alone again list bookable times; seats follow each room's Open window (not only Closed blackouts) (#1487).
5.5.0-beta7 14 Oct 2026 6 improved8 fixed1 removed
Improved
- Compatibility with WooCommerce 11.2.
- Timezone: abbreviation hover tip styles match across admin and My Account; the series list When column includes the abbreviation.
- Get_booking_form_timezone() prefers saved user timezone over the browser cookie; optional $user_id for customer-facing displays.
- Night span end advances in civil days (not PHP strtotime('+N night')). Tests: AvailabilitySpanEndDateNightTest locks night span end and night duration labels.
- Tests: TimezoneAxisLiveTest freezes Fake UTC evening "now" for positive-offset same-day past checks (#1478); FakeUtcAxisGuardTest fails the build if Fake UTC tokens are formatted with site wp_date() / date_i18n().
- Availability hub: read-only footer copy says "You can edit your own hours."
Fixed
- Availability hub: on small screens the WordPress admin bar stays fixed with Screen Options / Help instead of scrolling away.
- Availability hub: staff who can only manage their own hours can browse General and assigned product / location / resource rules read-only; Save stays on their Staff layer; Edit links hide without Create & Edit Products / Locations / Resources.
- Availability: night-duration span end dates advance in civil days, and summaries show civil When with an N night(s) label.
- My Account and order/email series times use the viewer timezone (saved preference, then browser, then site). Recurring pattern summaries and occurrence labels stay on the series booking axis so times no longer over-shift (e.g. 09:00 CEST shown as 18:00) (#1483).
- Storefront: same-day slots stay bookable on positive-offset stores (e.g. Australia/Sydney afternoon) (#1478).
- Availability / admin: day summaries, slot day keys, privacy export, save notices, and admin calendar day buckets keep appointment and slot civil days correct on non-UTC stores.
- Availability / slots: Automatic rooms Closed for a day no longer leave seats bookable; storefront and admin create require a free allocated room (name shows when Selection is Automatic, show name).
- Admin edit appointment: Locations & rooms experimental off no longer clears the room on save; Resources stays visible from history. Location change refilters rooms (concrete pick, no Automatic). Empty title area hidden; Created on / Save / Parent ID / Qty layout cleaned up.
Removed
- Product Duration: Night is no longer in the Duration dropdown (it was never a merchant option). Use Day for multi-day stays.
5.5.0-beta6 10 Oct 2026 1 new14 improved11 fixed
New
- AutomateWoo: appointment.location and appointment.resources workflow variables (#1460).
Improved
- Admin: What's New leads with the Availability hub; Locations & rooms and Meet are labeled experimental (off by default under Settings → Appointments). When the beta is off, CTAs open that setting. Mobile section tabs, topic cards with Previous/Next, and NEW/BETA title pills are clearer; copy matches menu labels and beta gates; Help → What's New is omitted on that screen.
- Availability hub: permanent left rail (title, description, search with layer filters, nested entity tree); full-width rule editor with Other rules that apply beside Week at a glance; layer and entity rows are real links; path toggle on small screens.
- Product Availability tab: Week at a glance (same strip as the hub, product + General) and a short Availability hub link; new products show General hours until a draft is saved.
- Product edit: Resources tab shows only a Locations nudge until the product has a venue (when locations exist); rooms still work alone when the shop has no locations.
- Settings → Appointments → Experimental: Locations & rooms off fully stops new booking use and hides related admin screens; past appointments still show their venue/room; product and CPT data stay for re-enable.
- Availability hub: new Open hours / Unavailable rules insert next to matching rows (not reshuffled on save); live search under the field with layer hit counts; timezone tip matches staff Availability; Restrict start days notes product weekdays; sticky rail docks without jumping.
- Product edit: cancel/reschedule "before the start date." aligns with the number and unit fields; help tips after Duration, Interval, Padding, Lead Time, Scheduling Window, Inventory capacity, Availability Check, and Tax status/class align with the controls.
- Storefront: location, staff, and resource extra costs use add-on style brackets (e.g. (+$5)).
- Admin timezone abbr: build_timezone_indicator_html; calendar/panel/hub inject adminTimezoneHtml / tzHtml. Removed unused short-name map API, adminTimezoneName, and writepanel abbr locals.
- WC_Appointments_Features::is_locations_resources_allowed() is the force-off filter; is_locations_resources_active() is allowed && Experimental checkbox. Filter woocommerce_appointments_locations_resources_active false force-offs (CPT/meta kept) and hides the setting. Appointment snapshot display stays ungated. Removed is_locations_resources_admin_visible(). Hub entities include staffIds, locati...
- RRULE match caches reset when store timezone identity changes mid-process. Admin settings page memoizes one settings instance. Tests: create_location / create_resource force Locations & rooms on after Features flag tests.
- Tests: IcsExporterStaleEntryRegressionLiveTest (#1480); TimezoneTest and TimezoneEnvironmentLiveTest (DST abbreviations); SlotsTimezoneLiveTest America/Chicago compare-start; ZeroTotalCheckoutSyncLiveTest (€0 settle, idempotency, stale cache).
- Tests: FeaturesLocationsResourcesFlagLiveTest; LocationsLiveTest staff capacity at location (#1475); MyAccountRescheduleContractLiveTest (#1474); Issue1470PriorityOverrideTest (product Open vs global full-day Closed by priority, dynamic + indexed); hub-rule-insert-index.test.ts for Availability hub new-rule insert index.
- Series preview: _dtstart_booking_axis makes normalize_preview_args() axis conversion idempotent; payload_from_body / cart preview skip a second normalize.
Fixed
- ICS export: VTIMEZONE DAYLIGHT/STANDARD use real DST transitions so EU zones keep last-Sunday March/October and strict clients no longer show events one hour late (#1480).
- Timezone labels: short abbreviations follow DST at the appointment time (e.g. London BST/GMT, Brussels CEST/CET) in emails, cart, and admin (#1479). Offset-style PHP abbreviations (GMT+0000) and Woo +00:00 zone IDs map to UTC/UTC±N instead of leaking into the storefront picker.
- Storefront: Default timezone shows city and abbreviation (e.g. Default (Ljubljana - CEST)); picker Default option uses the same site timezone as the form. Calendar bootstrap keeps site today selectable on positive-offset stores (#1478).
- Checkout: free (100% coupon / €0) appointment orders no longer re-fire completed and flood emails when object cache keeps a stale pending snapshot (#1477). Appointment Select2 styles no longer pad the country/state dropdown.
- Availability / slots: staff capacity fully overrides product capacity again (pre-5.5). Locations no longer re-clamp staffed slots by Inventory Capacity; room limits stay on Resource Max at once. Unstaffed products still count capacity per location. Locations without a matching room stay bookable on staff/product capacity; unscoped resources still close slots while venues exist (claim the resour...
- Storefront: staff and resource SelectWoo labels show name and avatar again (#1474); after changing location, the resource list shows only rooms for that venue plus shared (#1472); resource Select2 keeps its featured image after venue filtering; changing quantity no longer clears a still-valid selected time.
- My Account: rescheduling saves a newly selected location and resource (#1474).
- Availability hub: Week at a glance on a room includes the linked location's open hours and closed rules; Unavailable rules at the same priority no longer get reopened by earlier Open hours; search, rail, and pills omit disabled location/room layers; search keeps results until cleared, marks the open entity, and does not clip the focus ring; rail scroll stays put on pick and pins deep-linked row...
- Admin panel: create/edit availability checks include the selected room/resource so listed slots are no longer marked unavailable; panel notices show the server reason when a check fails.
- Night-duration products: checkout and add-on extended spans use Fake UTC civil day tokens and the date-slot path (like day products), so availability no longer rejects valid night bookings on non-UTC stores.
- Recurring series preview: store→booking timezone conversion on dtstart runs once. REST civil dtstart strings stay on the booking axis; repeating normalize no longer shifts weekly sessions outside open hours on non-UTC stores.
5.5.0-beta5 2 Oct 2026 2 new21 improved12 fixed1 changed
New
- Locations: optional additional cost per assigned location on the product Locations tab (signed; negative = discount). Staff and resource costs also accept negatives; appointment total still floors at zero.
- Rooms & resources: optional additional cost per resource on the product Resources tab, added to the appointment price like staff.
Improved
- Product Availability tab: Availability Check, Auto-select, and Restrict start days return here; open hours stay in the Availability hub (link on the tab).
- Germanized Pro invoices: appointment when, duration, location, resources, and staff sync onto new invoice line items (StoreaBill). Re-sync existing invoices from the order if they were created before this update.
- Settings → Appointments: Locations & rooms (beta) stays off by default under Experimental. Turning it off keeps menus, hub layers, and product tabs when the store already uses locations or rooms; booking times still honor stored settings.
- Admin panel: Staff, Locations, and Resources tabs share one picker-and-rows pattern (assign until Update). Manage / Add sit beside the title; row Edit opens in a new window. Staff rows show email and product count; Locations show kind, resource count, and optional cost (sortable when Customer selected); Resources show cost, Max at once, venue, and a featured image when set.
- Admin panel: Location and Resources tabs show when the beta is on, or when the product already uses them. With the beta off, Location stays on venue products; Resources stays on products that already require rooms, and on location products when published rooms exist.
- Product Location tab: drag venues to set storefront order when Customer selected (first is the default); leave empty for all venues A-Z.
- Storefront: customer-selected locations preselect the first listed venue so the calendar shows on load; customer-selected resources list only the product rooms (no blank Select), with the first in Resources tab order as the default.
- Availability hub: Title column is a Screen Option (on by default) for every layer; synced rules keep the Synced badge on the title when shown. Layout stays start-aligned like other admin screens, not centered.
- Storefront: first-month day availability is embedded in the product page again so the calendar paints without a second AJAX wait.
- Availability / slots: multi-staff month switches and dense calendars load faster.
- Admin panel: Inventory Capacity tips use appointments (not people); Resources field is Max at once with shorter tips; Resources label shows only when Selection is Customer selects; Location tips shortened.
- Availability hub: quieter chrome (empty-state placeholders, sidebar, search, week accent); Optional chip on layer blurbs; Save shows a loading state; entity Edit and hub links open in a new window with an external icon.
- Product resource_requirements rows are {resource_id, qty_consumed, capacity, cost}; allocation snapshots carry cost. WC_Appointments_Resources::get_qty() and _wca_resource_qty are removed.
- Product location_costs map (_wc_appointment_location_costs); snapshot _location.cost is stamped at write like resource allocation cost. get_location_choices() is request-cached.
- Locations: WC_Appointments_Locations::get_kind_label() is the shared In person / Online label.
- Storefront AJAX: one-shot flags skip postmeta/availability index re-checks and resource→location migration rescan; day-slots skips migration. Scripts use filemtime in the version; matching .min.js rebuilt.
- Slots perf: dynamic multi-staff month shares one occupancy normalize (index-path batch shape); WCA_SLOTS_PERF_LOG gates per-slot microtime phase timers; timed overlap day keys use arithmetic Fake UTC midnight.
- Rule priority: empty or omitted priority still defaults to 10 on save (hub, REST, classic form) and when applying rules.
- Tests: ProductConfigCheckLiveTest covers product name in no-appointable-times notices.
- Tests: ResourcesLiveTest (capacity, shared pool, order, cost, admin save, signed cost, empty-day occupancy when staff qty exceeds room capacity); LocationsLiveTest (allow-list order, location cost); AvailabilityHubDraftEntitiesLiveTest; FeaturesLocationsResourcesFlagLiveTest (beta-off escape hatch, Resources tab on location products when rooms exist).
- Tests: StorefrontDaySlotsBootstrapLiveTest locks page-boot day_slots hydrate vs build_day_data (GET location match keys; fixed location match key 0).
Fixed
- Admin panel: product config notices name the product with no appointable times, and the Availability hub dismisses that notice after General or product hours are saved when times exist again.
- Availability hub: product capacity notices show on the product deep link and open the matching open-hours rule; draft, pending, private, and scheduled entities appear with a status badge so you can set hours before publishing.
- Availability hub: typing a two-digit hour in From/To no longer loses the second digit; overnight Ends the next day floats on the To field as you type; save after a long-open tab retries once with a fresh security token.
- Availability hub: Week at a glance shows net available ranges (open hours minus recurring Closed), honors rule priority and month/week Closed rules that apply now, updates live while you edit, and on mobile stacks open ranges under the day name.
- Availability hub: deep links keep sidebar search and scroll only the entity list; mobile editor column stretches again; Unavailable empty state stays clear when only Google Calendar busy rules exist.
- Admin panel: product save no longer clears Location settings when the Location tab is hidden; Fixed mode keeps the selected venue; Resources filter follows assigned location rows.
- Storefront: resource select hides when the chosen location has no rooms, auto-selects the first room when the venue has any, updates when location changes, and matches location/staff spacing.
- Storefront: location/resource/staff changes coalesce into one calendar load; the loading bar stays until refresh finishes; month requests cover the visible month; repeat calendar AJAX no longer re-runs setup and migration checks.
- Storefront: days no longer look partially booked when staff capacity exceeds room capacity and nothing is booked (occupancy and "X left" follow real appointments).
- Availability / slots: when the requested range is already indexed, the calendar uses that data instead of falling back to the slower path while indexes catch up (live appointments still apply until backfill finishes).
- Availability / slots: rule priorities of 0 and below are honored on the dynamic path again so a Yes exception can override a Closed rule at priority 1.
- Checkout: products that use a room/resource no longer treat the customer's own in-cart hold as occupying that room when re-checking availability (same exclude as staff/location occupancy).
Changed
- Breaking change - Rooms & resources: Max appointments moved from the resource screen to each product's Resources tab. Re-enter values on products that use resources (beta values are not carried over).
5.5.0-beta4 22 Sep 2026 1 new13 improved9 fixed
New
- Availability / slots: location and resource open/closed rules apply when those entities are in the slot request. Empty rules add no constraint.
Improved
- Admin: What's New page highlights rooms & resources and the Availability hub alongside locations and Google Meet, with a light animated hero.
- Admin: Availability hub (Appointments > Availability) loads once with instant layer / entity navigation; Open hours and Unavailable editing; product, staff, settings, location, and resource screens link into the hub instead of separate rule tables. Product config notices for missing rules also link to the hub.
- Product General: Availability Check, Auto-select, and Restrict start days sit with duration / interval / padding; the product Availability tab is removed (hours stay in the hub).
- Availability hub: UI strings are localizable; the default layer is labeled General; Open / Closed / Busy use adjective translator context so they are not read as buttons.
- Staff permissions: separate Availability hub caps for global, product, location, and resource layers (plus existing own hours); staff without Set Available can only add closed dates.
- Admin: Availability hub uses quiet floating chrome on the WP admin gray with accents from the selected admin color scheme; Show priority and Show status live in Screen Options (status on by default; priority on for upgrades, off for fresh installs); when status is off, Open / Closed / Busy pills sit beside section titles.
- Admin: Availability hub week glance marks open days with a success accent; spacing, type scale, and touch targets tightened for clearer mobile-first editing.
- Availability hub: General rules say they apply to all products, locations, resources, and staff, and that rules on those levels override them.
- Kinds availability#location / availability#resource; cache scopes + DB 5.5.0 enum expand; merge weights global < product < location < resource < staff. REST v2 read/write.
- Availability hub: WC_Appointments_Admin_Availability_Hub bootstrap + AJAX save via save_rules(); admin-availability-hub.js / .scss with wp_localize_script strings. Removed classic global/product/staff rule tables, html-appointment-availability.php, and html-appointment-availability-fields.php.
- Staff permissions: manage_global_availability, manage_product_availability, manage_location_availability, manage_resource_availability (Full preset); hub filters layers/entities and ownership-checks save/delete IDs.
- WC_Appointments_Resources::authorize_resource_id() mirrors location authorize for posted occupancy; is_closed_for_window() skips Closed rooms when auto-allocating.
- Tests: AvailabilityLocationResourceRulesLiveTest covers location/resource Closed vs staff Open (same priority), Closed time-rule veto, priority escape hatch, and staff Closed vs location/resource Open.
Fixed
- Availability hub: mobile gutters match left/right; layer menu items get room inside the highlight; synced rule values sit closer to their labels; empty location/resource layer tabs stay hidden and tab badges are server-rendered so the tab row does not flash before JS paints.
- Availability hub: Week at a glance updates when you change a rule's range; datepicker date changes sync into the save payload.
- Rooms & resources: list table shows a featured-image thumb beside the title only when one is set; automatic allocation skips resources Closed for the booking window so cart validation matches displayed slots.
- Availability / slots: at equal priority, location or resource Closed (including time rules) vetoes product/staff Open; location/resource Open does not override staff Closed. Lower priority numbers still win as an intentional override.
- Storefront: time slots and cost calculation pass the selected resource into the slots engine (calendar already did), so resource Closed days no longer show bookable times or an available cost quote.
- Availability hub: staff without hub layer permissions no longer see or edit other layers; rule IDs must belong to the target entity on save/delete.
- Deleting a location or resource removes its availability rules (and indexed cache rows via the existing delete path).
- I18n: appointment change notes, resource-to-location migration lists, timezone titles, and last-synced timestamps only translate labels; arrows and date/time joiners stay fixed in code.
- Availability / slots: appointment index backfill only targets future appointments, so past bookings no longer block the gap queue.
5.5.0-beta3 19 Sep 2026 1 new14 improved7 fixed
New
- Rooms & resources: products can require shared resources (rooms, equipment) that limit availability across products. Qty 1 blocks every product that needs that resource once booked; higher qty allows concurrent use up to that limit.
Improved
- Product Resources tab: Label plus Selection (Automatic show name / Automatic / Customer selects); assign with venue-grouped checkboxes filtered by Locations (Select all / Clear per venue). Checked items are alternatives (one is allocated or customer-picked). Thumbs appear only when a featured image exists.
- Storefront: resource picker matches staff Select2 UI (under Location, before Staff); options filter by venue and stay hidden until a location is chosen when the customer picks the venue. Automatic has no form control.
- Product Inventory: Capacity / Min / Max copy describes people spots (not resource quantity). Resource Max appointments is concurrent bookings; Location is required when venues exist (one resource per venue).
- Locations: Resources sidebar lists rooms for that venue; Manage and Add respect the manage resources permission.
- Appointments list: optional Resources column (Screen Options) shows allocated titles; Location and Resources stay hidden by default.
- Admin calendar: events and hover tooltip include resource titles (with location).
- ICS export, Google Calendar description, CSV export, and the woocommerce-appointments/resources email personalizer tag include allocated resources.
- Order confirmation, My Account, order emails, PDF invoices, and appointment emails show the assigned resource when Automatic show name or Customer selects is on (admins always see it).
- Location and resource Products sidebars: whole row is clickable on hover. Resource edit lists products beside Featured image under Publish; list table shows the featured image beside the title.
- CPT wca_resource with _wca_resource_qty, _wca_resource_location_id (required venue when locations exist), notes, featured image, and used-by products sidebar. Product props resource_requirements, resource_label, resource_assignment (hidden|display|customer); appointment resource_allocations plus repeatable _appointment_resource_id for lookups.
- Availability cache: resource_id column and updated uniq_occurrence via ALTER; resource rows in insert_appointment_row. Slots: OR-pool remaining seats, location scope, concurrent Capacity (no class-window dedupe). Booking lock acquires per-resource keys with the product lock.
- Write sites auto-populate allocations (posted data, booking handler, admin AJAX/classic/panel/add, REST, series materializer, recurring cart occupancy). Staff permission manage_resources (Full preset). Migration assigns unscoped resources to a venue; removed share modes (per_location / per_staff).
- Tests: ResourcesLiveTest covers cross-product qty, Capacity, cancel-frees-pool, write path, location-scoped rooms, migration, and dynamic vs indexed parity. IndexedAvailabilityParityLiveTest keeps GCal-busy Fridays out of the critical bookable set.
- Tests: RestAppointmentPanelUpdateLiveTest locks schedule-only PUT preserving staff/location and calendar row.
Fixed
- Rooms & resources: Automatic allocation picks a free alternative in the OR pool for the booking window and location (no restacking a busy room; no wrong-venue rooms).
- Availability / slots: required resources gate slots even with no existing resource bookings; OR-pool "N left" sums free alternatives.
- Classic appointment edit and admin panel create/edit/view: resource select (filtered by location when needed), view links to resource edit when permitted, and save preserves an existing pick instead of blind re-auto.
- Admin panel: floating labels stay raised when a select has a preselected or locked value.
- Admin panel: editing date/time no longer clears staff or location (Select2 sync no longer re-enters change handlers), so appointments stay on the calendar after save.
- Admin panel: create Product select always shows a search field so typing filters even with fewer than 10 products.
- Recurring: series occurrences receive resource allocations from the product when materializing.
5.5.0-beta2 14 Sep 2026 10 improved11 fixed
Improved
- Staff permissions: View Product Details is folded into View Assigned Products (pricing, duration, and capacity included). Create & Edit Products remains the edit gate.
- Admin panel and appointments list: product and location titles link to their edit screens only when the user can manage products or locations.
- Staff customers: table fits the viewport without horizontal scroll (secondary columns drop on narrower screens; mobile uses stacked cards); toolbar search matches select height; Appointments count aligns with its column header.
- Appointments list: Location column (snapshot title), hidden by default like Add-ons.
- Staff dashboard: location filter beside the Dashboard title (default All locations) scopes upcoming, week, insights, heatmap, chart, and top products when more than one location exists; Welcome date shows the staff profile timezone abbreviation.
- Settings → Staff Permissions: permission list uses plain rows instead of bordered cards; matrix keeps the standard settings column layout.
- Locations: In person / Online type control is a connected segmented toggle so it reads as one exclusive choice on all widths.
- Locations: removed staff assignment on the location edit screen (and the Locations Staff / Users Locations roster columns). Staff stay on the product; location is where/how only. Multi-location no longer filters staff by venue. Admin calendar and staff dashboard location filters list all published locations until optional staff-profile assignment exists.
- Removed location staff intersect helpers, storefront/admin staff_by_location filtering, and get_locations_for_staff(). REST slots location_id scopes occupancy and product allow-list only (no staff narrowing).
- Security: audit notes in docs/plans/security-audit-2026-09.md; SeriesRestLiveTest covers customer series create forbidden and manager idempotency key required; guest slots/index throttle via wc_appointments_rate_limit_max_requests; Google Calendar OAuth uses signed expiring state; refresh tokens stay plaintext (legacy wcaenc: decrypted once on read).
Fixed
- Security: staff without Manage others appointments cannot edit or delete others' appointments; confirm, bulk status, create appointment, product staff AJAX, and Google Calendar AJAX enforce the same capability rules.
- Security: recurring series REST create is limited to users who manage appointments (customers create series through checkout); availability rules and appointment index REST require edit capability; guest slots and availability index reads are rate-limited.
- Security: appointment export only serves files under uploads; Google Calendar sync stores Busy instead of personal event titles; OAuth connection state is signed and bound to the admin who started it.
- Storefront: staff names on the appointment form are escaped (XSS).
- Appointments list: product filter shows its placeholder when no product is selected; When column timezone abbreviation tooltip is no longer clipped.
- Staff dashboard: Upcoming Appointments eye icon opens the appointment view panel; dates, times, Insights, This Week, heatmap, and chart use the staff profile timezone.
- Admin: classic appointment edit screen shows and saves Location.
- Admin panel: view panel from the appointments list or Upcoming Appointments opens immediately (skeletons while details load).
- Locations / product Staff: Manage Staff is hidden without Manage others appointments; location Used by hides Manage Products and Add Product without Create & Edit Products.
- Admin: Dashboard no longer gets a blank top gap when WP_DEBUG display is on and a product add-on is missing the display key (panel preload no longer emits PHP warnings that trigger WordPress php-error).
- Storefront: after checkout, leftover appointment cart lines linked to a paid or settled order are cleared without the "removed due to inactivity" notice.
5.5.0-beta 10 Sep 2026 2 new17 improved14 fixed
New
- Locations: appointable products can use fixed or customer-choice locations (physical or online); bookings store a location snapshot for cart, emails, My Account, ICS, and Google Calendar.
- Virtual meetings: Google Meet links are created via Google Calendar sync for online locations when the product meeting provider is Google Meet; emails, My Account, ICS, and admin panel show a Join Meeting line.
Improved
- Staff Permissions: Manage Locations controls whether staff can open the Locations admin (off by default; included in the Full preset).
- Storefront / admin panel: location pickers use the same searchable select pattern as staff.
- Locations: edit screen uses In person / Online chips, floating labels, and a two-column layout; sidebar lists products that use the location; Staff select shows avatars.
- Admin calendar: filter panel keeps Products, Staff, and Locations columns with status above; Locations hides when unused and includes No location; events show location on hover; live SSE updates include location.
- Virtual meetings: Join Meeting shows an Open meeting link in the admin panel, emails, and order summary (plain-text emails still include the full URL).
- My Account: appointments list shows Join (Meet or static online URL) with a starts-in countdown; pending Meet without a URL notes that the link will appear when ready.
- Product Location tab: matches Staff (toolbar list, Manage/Add); Location is None, Fixed, or Customer selected with an optional Label; Customer selected offers every location unless two or more are listed; Video meeting shows only for Online locations.
- Product Staff tab: staff rows use a larger avatar with aligned edit/remove controls; assign picker stays at the standard Select2 height.
- Admin panel: location field uses one floating label (no duplicate placeholder); view location icon matches other outline key icons.
- Locations: CPT wca_location; product location_mode / location_ids / location_label; appointment location_id + snapshot; LocationsLiveTest covers write, staff eligibility, per-location occupancy, customer-choice offer rules, get_product_ids_using_location, and custom labels.
- Locations admin: CPT fields reuse .wca-padding-float; type chips toggle physical/online with CSS :has().
- Virtual meetings: product meeting_provider, appointment _wca_meeting_* props, GCal conferenceData createRequest; MeetingsLiveTest covers create, apply from event, display preference, cancel clear, and My Account join context.
- REST: slots accept location_id; appointments schema and series payload document location_id, location, and location_display.
- Tests: ProductConfigCheckLiveTest covers empty staff/location warnings; SeriesManagerLiveTest covers series location write and location-scoped conflict preview; MalformedAvailabilityIndexLiveTest covers expired staff rules vs index readiness.
- Availability / slots: wc_appointments_availability_range_type_is_expired() shared by indexer and readiness; overlap index reuses the query product; calendar get_collection uses for_availability without enabling L1.
- Google Calendar: event query strings use http_build_query (conferenceDataVersion and fields).
- Storefront: appointment form querystring sync uses one wca_update_querystring batch path; removed regex replace/remove helpers.
Fixed
- Availability / slots: max bookings per slot is counted per location; multi-staff calendars and automatic assignment only use staff eligible at the selected location.
- Indexed availability: leftover pending rule IDs no longer keep the storefront on the dynamic engine forever; expired rules no longer block index readiness and are purged on expire or reindex.
- Locations: product edit warns when none of the product's staff are listed on an allowed location; "all staff together" and slot checks use only location-eligible staff.
- Locations: Customer selected with two or more venues asks for a location first (staff and calendar stay locked until chosen); empty list or a single leftover id still offers every published location; one published location is used automatically.
- Locations: storefront location select2 shares staff field styles; list column Type replaces Kind.
- Admin calendar: on small screens the admin bar stays pinned, the toolbar sits flush, and Add New / Today / date stay on one baseline; filter columns wrap instead of scrolling sideways.
- Admin calendar: switching to week or day view scrolls to now (or 08:00) instead of throwing a JavaScript error.
- Admin: classic Create Appointment page persists the location ID and snapshot (same as the panel path).
- Admin panel: create and edit respect the selected location for capacity; Customer selected with two or more venues hides staff and date/time until a location is chosen; empty allow-list cannot stay Fixed; staff options filter by location.
- Admin panel: clearing Staff (e.g. after a location change) no longer leaves the old staff name under the floating label.
- Admin panel: recurring create writes location onto the series; view shows Join Meeting from the calendar event with Location before Meeting.
- Recurring: series preview, conflict checks, create, and reschedule respect location capacity like single bookings; My Account series view, series emails, and order summary show the series location.
- Storefront: selected date in the appointment form uses the site language and date format, not the browser locale.
- Storefront: customer-selected location is kept in the page URL (with date, staff, and time) so refresh and share links restore it; restoring location from the URL no longer clears date and time.
5.4.15 4 Sep 2026 4 improved4 fixed
Improved
- Compatibility with WooCommerce 11.1.
- Tests: AddonCartCostLiveTest covers day/night duration add-on fees (stack, zero-base, duration-only fee 0, fee without duration).
- Tests: CartCheckoutLiveTest covers dangling order parent on inactive-cart cleanup and session load; hard-deleted orders assert cascade appointment removal.
- Tests: weekday collectors treat max_bookable civil day as exclusive (partial max_check_to window; Johnsansom parity stable on America/New_York mornings).
Fixed
- Product Add-ons: "Use once per recurring series" follows Can be recurring? on existing and newly added fields, including when the checkbox is toggled before save.
- Product Add-ons: day/month/night products always include duration add-on fees (zero-base packages and priced bases); extended days still bill at the product rate. Duration-only options stay available: set the option price to 0 so the span grows and only the product rate applies.
- Product Add-ons: a required Quantity (input multiplier) with min 0 again accepts 0 (base duration only; no add-on cart line).
- Cart: deleting a draft (or any) order as cron/Action Scheduler again releases linked in-cart holds; abandoned-cart cleanup no longer treats a missing order ID as a live exemption.
5.4.14 3 Sep 2026 10 improved4 fixed
Improved
- Admin calendar: availability SSE snackbars name the change again (saved or deleted; bursts still collapse to a count).
- Admin calendar: reorder-only availability rule saves no longer show an SSE snackbar.
- Updated all translations for new and changed strings.
- Tests: live suite no longer strips shared WP/WC hooks to silence SSE; pushes gate via wc_appointments_sse_push_enabled, runtime queue reset, safer cache flush, and phpunit memory_limit 768M.
- Tests: CartCheckoutLiveTest covers multi-line same-slot checkout (2 allow / 3 reject), confirmed-plus-holds, different times, staff-scoped holds, and single-hold qty 2; index on/off where capacity exclude matters.
- Tests: BookingFormAtcLiveTest covers storefront add-to-cart capacity (confirmed/in-cart fill, qty above remaining, remaining seat allow) and sold-individually blocking a second cart line.
- Tests: BookingScenarioWritePathLiveTest covers padding×capacity, per-day multi-line, staff automatic/all ATC, requires-confirmation occupancy/mixed cart, overnight multi-hold, addon×capacity, customer TZ ATC, unpaid occupancy, cross-product padding ATC, span=start ATC, admin vs storefront capacity parity, restricted days / min_date crafted POST ATC, multi-day overlap ATC, cancelled/was-in-cart...
- Tests: MyAccountRescheduleContractLiveTest covers reschedule self-exclude, full-slot reject, capacity-2 remaining seat, padding adjacent reject, and staff-all taken reject; CartCheckoutLiveTest locks Store API vs legacy checkout wrappers on multi-hold capacity; RestAppointmentsCreateLiveTest documents that REST create may overbook a storefront-full slot.
- Tests: GcalStaffRemoveOnCancelLiveTest locks remove_from_gcal() staff fan-out: staff-only sync, main plus staff, and shared calendar with and without a main event (one DELETE per event).
- Tests: IcsCancelContractLiveTest locks cancelled ICS export (no STATUS:CANCELLED) and that cancel emails skip ICS attachments; EmailCancelTransitionLiveTest covers single cancel notifications, series FUTURE suppress, and series ONE per-occurrence cancel; GcalInboundCancelLiveTest covers Google→WP CANCELLED (main cancel, staff detach, last-staff cancel).
Fixed
- Checkout: two or more separate cart appointments for the same product and time slot no longer fail availability when their combined quantity still fits capacity.
- Google Calendar: cancelling or trashing an appointment now removes the event from staff calendars when only staff calendars are connected (no site calendar or no site event).
- Google Calendar: successful event DELETE clears the stored site/staff event IDs; inbound staff detach drops that staff’s event ID (staff event ID map merge no longer blocks removals or updates).
- Admin calendar: fixed toolbar and day headers use locked SCSS spacing (no JS height measure); left tracks the admin menu via WP folded breakpoints so Chrome and Firefox stay flush.
5.4.13 2 Sep 2026 2 improved1 fixed
Improved
- Validation clones the product to the posted duration for range checks (get_product_for_range_validation); extended-range capacity anchors on a start-aligned grid row (±120s) with a covering-row fallback for off-grid starts; a zero or negative posted/list total keeps the base duration (shorten below base stays allowed); slot cache keys include duration so adjusted-length collections never reuse...
- Tests: AddonDurationValidationLiveTest covers handoff after a prior booking (+30/+60), shortened 19:15-20:00 (−15), index on/off, and listing vs cart; AddonCartCostLiveTest locks posted duration below base.
Fixed
- Storefront / cart: Product Add-ons that lengthen or shorten duration book the same times shown in the list, including starts right after another appointment ends and late starts that only fit a shortened length.
5.4.12 28 Aug 2026 4 improved5 fixed
Improved
- Updated Product Add-ons to 8.4.1 (validation, Store API batch, global add-ons search/sort, upload index.html, WordPress Abilities).
- WooCommerce minimum supported is 10.6; WordPress minimum is 6.8 (aligned with Product Add-ons).
- Updated all translations for new and changed strings.
- Tests: AddonSeasonalDuplicateOptionLiveTest covers radio/checkbox purchase when a hidden seasonal option shares the same label; AddonCartCostLiveTest covers cart duration fields, posted duration totals, and hour vs day addon cost rules; AddonStorefrontTitleLiveTest locks fieldset Title (Label/Heading) markup for checkbox, radio, and images, plus image swatch title omitting price when hide price...
Fixed
- Product Add-ons: radio/checkbox options with the same label as a hidden seasonal duplicate can be purchased again; cart uses the visible option’s price (not the hidden one).
- Product Add-ons: hide price and duration labels work again on the storefront after the 8.4.1 update (including image swatch tooltips); option prices no longer wrap onto their own line.
- Storefront: add-on Title (Label/Heading from the product editor) shows again for checkbox, radio, and image fields; fieldset/legend styles match upstream so titles do not collapse.
- Product Add-ons: cart again skips TM Extra Product Options items when addons carry epo, so both extensions can coexist after the 8.4.1 sync.
- Admin panel: header/footer scroll shadows match the calendar chrome and appear from the first pixel of overflow, drawing over fields scrolled under the edges.
5.4.11 26 Aug 2026 3 improved1 fixed
Improved
- Product Add-ons: addon name font size and weight are no longer !important, so themes and custom CSS can restyle them; display stays !important so poorly styled themes do not break legend/label layout.
- Tests: PhpstanLevel2RegressionLiveTest covers Failed → processing/completed restoring an order-linked was-in-cart hold via publish_appointments.
- Admin CSS: clearfix no longer emits IE *zoom; calendar tablenav date/week selectors drop redundant inline-block under float (rebuild admin.css).
Fixed
- Orders: paying a previously Failed order restores an order-linked appointment stuck as Was In Cart (e.g. Apple Pay retry after Auth Denied).
5.4.10 20 Aug 2026 3 improved4 fixed
Improved
- Compatibility with WordPress 7.1.
- Opt-in Playwright smoke: npm run test:e2e (staff change + Show first available against staff-auto-select-in-separate-month on bookingwp.local).
- Tests: GcalSharedCalendarMirrorLiveTest covers foreign busy import and write-back skip; GcalSyncUpdatedDescriptionLiveTest locks the description filter BC.
Fixed
- Storefront: changing staff with auto-select on keeps the selected date and time when still available; otherwise shows Show first available next to No slots available.
- Google Calendar: appointments from another site on a shared calendar import as busy rules (foreign site_url). Editing that busy rule no longer overwrites the Google appointment event.
- Google Calendar: restored woocommerce_appointments_gcal_sync_updated_description on event updates (dropped in the 5.0 sync rewrite).
- Admin panel: changing day or a duration add-on no longer jumps the form scroll back to the top of the mini calendar.
5.4.9 15 Aug 2026 6 improved6 fixed
Improved
- Admin panel create: WooCommerce Deposits uses the same outlined card as billing, with pay-deposit guidance and payment plans only while paying a deposit.
- Admin panel browse: month availability includes the day’s time slots in one request; open browse joins the in-flight prefetch.
- Browse helpers: one day_slots_by_date map with shared month/day cache keys; dropped unused month abort wiring.
- Tests: CartCheckoutLiveTest covers session load reverting was-in-cart holds and inactive-cart cleanup skipping order-linked holds.
- Tests: AddonDurationSlotListingLiveTest covers get_slots AJAX listing vs cart for +60m, +30m, −30m, minute-unit, and span=start.
- Emails: dropped native return/param types on subclassable WC_Email_* override points (PHPDoc kept); PHPStan ignores missingType.return / missingType.parameter on includes/emails/class-wc-email*.
Fixed
- Cart: logging back in during checkout restores the in-cart hold; abandoned-cart cleanup no longer deletes a hold already linked to an order.
- Admin panel: mini calendar marks a day available only when at least one slot has enough capacity for the quantity field (same rule as the time list).
- Storefront: time slots refresh for the full add-on duration (longer or shorter), so late starts that no longer fit are removed from the list.
- Emails: custom theme subclasses of appointment emails no longer fatal on PHP 8 when they override trigger() or init_form_fields() without a : void return type.
- Storefront: add-ons after the calendar appear once a date (or month) is chosen, so extras that change duration stay visible when no times are left.
- Admin calendar: third-party admin notices no longer push the grid down; calendar errors still show flush under the toolbar.
5.4.8 8 Aug 2026 1 new10 improved12 fixed
New
- Appointable product: set Padding Before and After separately on one row. Existing padding stays Before; After defaults to 0.
Improved
- Availability / slots: the gap between appointments uses After from the earlier booking plus Before from the later one (timed, day, and shared staff). With only Before set (the default after upgrade), identical products keep the same handoff gap.
- Admin panel: create/edit recurring uses the same outlined card as billing and add-ons; schedule fields use compact floating labels in one row.
- Admin calendar: on narrow screens the period title opens a month or date picker (prev/next hide); week and list show a month label; tapping a month day opens Day view and Back returns to that month.
- Padding: padding_duration remains buffer before; added get_padding_after_duration(), get_padding_after_in_minutes(), set_padding_after_duration(), and get_padding_before_in_minutes() (alias of get_padding_duration_in_minutes()). Overlap builders and per_day_booked_totals reserve both sides; day/month get_padding_days_for_fully_booked_dates() expands (before + after) days on each side. Meta _wc_...
- Tests: PaddingBeforeAfterLiveTest (timed/day, shared-staff gaps, cancelled, empty-window, dynamic vs indexed); ProductConfigLiveTest and SlotsScenarioTest lock 24h hourly listing and after-only / before+after index parity; ProductConfigCheckLiveTest skips notices on simple products; wca-when-deps-ready.test.ts for wcaShared.whenDepsReady.
- Tests: opt-in npm run test:php:stress and npm run test:php:smoke; default npm run test:php stays full unit+integration. Clean test-blog setup in tests/phpunit-env.local.example.
- Integrations: WC_Appointments_Integration_Page_Optimizers opts form JS out of LiteSpeed / Autoptimize / WP Rocket Delay-Defer on form pages. Appointable product General number/unit widths moved into admin.scss.
- Admin panel: create opens upper-biased on narrow screens; tighter floating-label insets; availability dims while checking; status chips match border to label on create/edit; customer search drops the clear control; overnight +Nd sits beside the End date floating label.
- Admin calendar: Screen Options/Help tuck with a peek and hide the toolbar when open; toolbar stays fixed under the admin bar; borderless period/view controls; wider padding on large screens.
- Storefront: Customizer Selection color skips inline CSS when it matches the default.
Fixed
- Storefront: multi-day day-duration products again highlight the booking span after picking a start day; timezone abbr shows one tip; the form still initializes when page optimizers delay jQuery or script params.
- Availability / slots: 24h duration with an hourly grid again lists each hour when the next day is also appointable.
- Admin panel: Select2 aligns on create from Add New; focused fields scroll above the mobile keyboard; mini calendar fits narrow panels; edit keeps Update in the footer (disabled until dirty).
- Admin panel create: Product Add-Ons date picker no longer closes the panel; footer cost refreshes when add-on prices change without duration.
- Admin panel: product dropdown lists appointable products again on non-calendar screens; customer search and Customer label translate on standalone create/edit.
- Admin panel: Pick from calendar keeps the selected day for staff timezones; day/all-day edit availability uses civil dates; mini calendar refreshes when product, staff, or quantity changes; switching product on edit updates date/time for the new type without jumping the panel.
- Admin panel: staff on unstaffed products is optional for managers only and ignored in availability checks.
- Admin calendar: Order Total after panel create is correct; narrow layouts keep the filter panel, month cells, and timed events in bounds; day headers stay flush under the fixed toolbar with admin notices below.
- Admin calendar: midnight appointments show when staff timezone differs from the site; opening a view scrolls to now or 08:00 in week/day, and to the top in month/list.
- Admin calendar: Screen Options and Help are usable again for admins and staff (WooCommerce sticky header no longer covers them).
- Admin: legacy Create as a Customer again books for the selected customer; configuration notices no longer appear on non-appointable products.
- Appointable product: Padding Before/After keeps equal gap when the unit wraps and no longer sits flush against the field above.
5.4.7 3 Aug 2026 15 improved15 fixed
Improved
- Admin panel: create/edit fields use outlined floating labels (Checkout-style); status uses compact filter chips; attendance is hidden on create, editable on edit, and muted text on view; Pick from a calendar stays a compact trigger until opened.
- Admin calendar: sticky toolbar and day headers under admin chrome with one page scrollbar; the toolbar docks under the admin bar while you scroll.
- Admin: calendar/panel product list loads with a light id/title/color query instead of hydrating up to 200 full product objects on every admin load.
- Admin panel browse: day availability evaluates each rule once; all-day RRULEs reuse the in-request path instead of rebuilding minutes every civil day.
- Tests: AdminCalendarAvailabilityStripeTimezoneLiveTest (PHP) and admin-calendar-availability-rule-timezone Vitest lock civil vs timed stripe timezone policy for staff UTC, Australia/Perth, and America/Los_Angeles.
- Tests: SeriesCartCheckoutLiveTest covers series creation on on-hold, processing, completed, and partial-payment (pending is not hooked).
- Tests: RuleMinutesRruleCivilDayTest, RruleCivilDayAvailabilityLiveTest, RuleMinutesOvernightApplyEvaluateTest, and OvernightTimeAvailabilityLiveTest cover overnight/all-day RRULE civil-day parity and time:N overnight vs end-at-midnight.
- Tests: SlotsTimezoneLiveTest and AdminPanelTimezoneLiveTest cover store/staff timezone boundaries for slots and panel availability (Fake UTC chip fields).
- Tests: IcsExporterStaleEntryRegressionLiveTest covers offset-based store timezones (gmt_offset / +01:00) so VTIMEZONE generation does not fatal when getTransitions() returns false.
- Build: npm run build runs test:js (calendar timezone Vitest) alongside gcal and PHP suites.
- Admin panel: primary form controls use 14px body text; series list shows date and time ranges on separate lines.
- Admin panel create: browse no longer auto-opens when a slot is unavailable; Add New without calendar prefill leaves date/time empty (date-only calendar picks still auto-select the first time).
- Admin panel: product select uses preloaded products (instant); clear (x) removed on create/edit; duplicate titles append SKU or #id; duration sits on the When row; line Total shows only when it differs from order total.
- Admin calendar: compact one-row toolbar; Add New is a leading filled button (label on wide screens, icon-only on narrow); filter button shows an active-count badge only (selections stay in the filter panel).
- Admin calendar: hovered and selected events use a white ring plus lift; selected events rise above overlaps.
Fixed
- Admin calendar (staff): appointments you create for products without assigned staff stay on the calendar immediately (creator matches the locked staff filter, same as after reload).
- Admin panel create: real midnight slots (00:00), including admin-TZ midnight, keep a timed calendar preview and availability checkmark (slot timestamp distinguishes them from date-only placeholders).
- Admin panel: narrow view fills the viewport; series uses panel scroll; customer timezone and cost lines stack; series rows are date | status | open.
- Admin calendar: switching the appointment panel from view to edit no longer flickers the calendar event (preview ghost only appears after the draft schedule changes).
- Admin calendar: full-day unavailable stripes stay on the civil day (not shifted by admin timezone); stripe tips on hover/tap instead of a modal; tapping a stripe no longer opens create; empty cells open create again.
- Admin calendar: sticky WooCommerce header chrome scoped to the calendar page only; staff Screen Options/Help no longer inflate Firefox scroll; empty preload ranges still seed so first paint skips REST; availability rules load again when indexed cache is enabled.
- Admin calendar SSE: after a recurring series materializes, the view panel gets the full occurrence list and package pricing; one series create snackbar with customer and pattern.
- Admin calendar SSE: new appointments stay after month navigation (cache insert); payloads build only when queued and flush once per request; burst creates/bookings collapse to one snackbar count.
- Admin calendar/panel (staff): product preload respects Create Appointments for Any Product; create auto-selects the logged-in staff when assigned; toolbar Add New uses the same path as slot-pick (no full reload).
- Admin panel create: required date/time errors show under those fields and clear when valid; hourly auto-select no longer rolls the form date forward when staff timezone differs from the site.
- Admin panel: Staff field shows or hides from product preload (same as add-ons); create availability responds faster; day-duration checks use the correct civil day when staff timezone differs; timed availability keeps slot timestamps when site and admin browse days differ.
- Admin panel browse: skeleton cells under a kept header; day click and view toggles reuse in-memory data; SSE invalidates browse caches while open; product switch reloads the open mini-cal.
- Availability / slots: overnight RRULE blocks (e.g. weekly Fri 22:00-02:00) again split across civil days (evening on the start day, morning on the next).
- Storefront recurring: create the series when the order reaches on-hold (cheque/BACS), not only processing/completed, so offline payment checkouts still materialize appointments.
- ICS export: offset-based store timezones (e.g. +01:00 from gmt_offset) no longer fatal VTIMEZONE generation when DateTimeZone::getTransitions() returns false.
5.4.6 30 Jul 2026 1 improved2 fixed
Improved
- Tests: global-only availability with indexing enabled (plain and staff products), index readiness ignoring malformed scoped cache rows, and product config probe when global rules start after the initial 14-day window.
Fixed
- Admin panel: product configuration probe scans the booking horizon in capped chunks (14/30/60 days) instead of only the next two weeks, so global rules or RRULEs with a later start do not trigger false "no bookable times" notices.
- Indexed availability: index readiness and multi-staff slot generation align with fetch guards (malformed scope rows no longer mark the index ready; empty indexed batch falls back to dynamic/global rules); fixes global-only rules not applying and false product config notices after 5.4.5.
5.4.5 30 Jul 2026 3 improved5 fixed
Improved
- Admin panel: Product edit shows configuration notices at the top when saved settings may prevent bookable slots; notices distinguish misconfiguration from fully booked inventory (slots engine diagnostics) and link to the matching Appointments tab.
- Admin panel: Clearer help tips on duration, interval, availability check, and rule quantity fields.
- Tests: ProductConfigCheckLiveTest, MalformedAvailabilityIndexLiveTest, and StorefrontSlotPickerMarkupLiveTest cover product config probe, malformed indexed rules, storefront slot notice markup, and getInstanceContext() form-element resolution.
Fixed
- Indexed availability: product- or staff-scoped rules with an empty kind_id no longer leak as global rules (read-side scope guard, write-side skip on index); manual re-index reports skipped malformed rules; get_title() returns empty string for NULL legacy titles (avoids PHP 8 TypeError on re-index).
- Admin panel: Configuration check and probe use the slots provider only (indexed availability, staff merge, index fallback): no false duration-vs-window warnings on full-day or weekday rules; priority-based engine diagnostics; Availability check mismatches when slots exist but none qualify as bookable starts.
- Admin calendar: month view highlights today with the same badge as week and day views; legacy calendar falls back to week view when the saved view is List (React-only).
- Storefront time picker: getInstanceContext() works when passed the form element (legacy theme markup); empty-day and AJAX error notices use consistent slot_row / ul / li.slot markup (full width, no bullet).
- Storefront date picker: multi-staff autoselect uses staff-aware calendar day_data (async month load); autoselect products skip full-range default-date scan; non-autoselect uses chunked first-slot for the pre-selected provider.
5.4.4 27 Jul 2026 1 improved2 fixed
Improved
- PHPStan raised to level 5 - type narrowing, expanded plugin and WordPress stubs, and dead-code cleanup across admin, data stores, API, GCal, and integrations (clean baseline).
Fixed
- Product Add-ons integration: decode jQuery .serialize() form payloads with wp_unslash() before parse_str() in legacy cost filter.
- Admin calendar: guest customer labels on HPOS stores read billing from wc_order_addresses (not legacy _billing_* order meta), restoring "Name (Guest)" on calendar preload and SSE updates.
5.4.3 26 Jul 2026 8 improved10 fixed1 removed1 changed
Improved
- Admin calendar preload: eligible-ID query plus batched postmeta hydration; batched order meta (no per-order WC_Order hydration). Recurring series package summary on bulk fetch (get_panel_preload, one load per series id); full appointment panel still loads on open.
- PHPCS: WPCS security ruleset (phpcs.security.ruleset.xml, npm run lint:php:security), shared path rulesets and request helpers; lint:php / build gate runs security scan + PHPStan; optional lint:php:standards / lint:php:quality; PHP-CS-Fixer via format:php.
- PHPStan raised to level 4 - dead-code cleanup across admin, slots, API, GCal, and core (clean baseline).
- Rate limiting: filterable enabled, guest_only, and strict_max_requests; named guest-read defaults on WC_Appointments_Rate_Limiter.
- Tests: PublicAjaxSecurityLiveTest, PhpstanLevel4RegressionLiveTest, IcsExporterStaleEntryRegressionLiveTest, MyAccountRescheduleContractLiveTest, CalendarPreloadParityLiveTest (calendar preload vs independent eligibility SQL; non-UTC and all-day; attribute term labels), RequestBoundaryRegressionLiveTest, SanitizerBeforeParseGuardTest, and SseTopicContractVerificationTest (request transport boun...
- Admin panel: remove Cancel from create/edit footer (close via X / backdrop).
- Admin calendar: Google-style grid (horizontal week lines only in month, stacked week/day headers, centered month dates); neutral toolbar chrome (admin body text/borders), segmented view switcher, primary + Add New beside Filters (icon-only; label on hover/aria).
- ICS export: log a warning when unloadable entries are skipped (WooCommerce → Status → Logs).
Fixed
- Admin calendar: toolbar spans full wrap width below Screen Options/Help; staff without WooCommerce header get Add New in the toolbar; staff/creator filter uses EXISTS (not INNER JOINs); Fake UTC civil preload range and YmdHis row parsing (not strtotime); shared excluded-status list (cancelled visible; holds/trash/drafts hidden) on SQL query, legacy API, and frontend; orphan/deleted post_parent...
- REST API: appointments get_item_schema() extends the parent schema instead of starting from null.
- Security: sanitize/unslash POST/GET at origin across storefront AJAX, data stores, cart, SSE, cron, widget, admin (AJAX, settings, calendar, list table, exporters, meta boxes, product edit), WPML, Google Calendar, and integrations; prepared SQL for lookups and migrations; escape output in admin views; guest-only rate limit preserved for cached pages with optional nonces.
- Storefront/admin AJAX: parse jQuery .serialize() form payloads before per-field sanitization (not sanitize_text_field() on the encoded string - destroys %XX sequences for timezones, times, and add-on text).
- SSE: preserve dotted event names (appointment.created) in the stream; sanitize_key() stripped dots and broke admin calendar live updates.
- Availability rules JSON: decode unslashed payloads first (callers pass wp_unslash( $_POST )); double-unslash broke rule titles containing quotes.
- Admin calendar: week/day column headers use browser-local date (not UTC parts); legacy week/day data-time and calendar_day= links use site timezone (not gmdate); batched order line item meta resolves global attribute term names (not raw slugs) in the appointment panel.
- Add Appointment and My Account series actions: read REQUEST_METHOD from $_SERVER (not filter_input( INPUT_SERVER ), unreliable under some SAPIs).
- Legacy install migration: delete product availability post meta by key after table migration (not a single rule value).
- ICS export: skip unloadable appointment entries so stale IDs do not fatal calendar generation.
Removed
- WC_Product_Appointment_Rule_Manager::get_minutes_from_rules() remains as a compatibility shim; use WC_Appointment_Slots_Rule_Resolver::build_minute_mask() instead.
Changed
- Breaking change - Availability: decode_rules_from_post() requires the unslashed POST array argument (no default); third-party save handlers must pass wp_unslash( $_POST ).
5.4.2 18 Jul 2026 3 improved2 fixed
Improved
- PHPStan raised to level 3 (phpVersion 7.4).
- Tests: regression coverage for range_type RRULE routing parity and orphan rrule field cleanup.
- Tests: lock GCal failure-path contracts (make_gcal_request() → WP_Error, sync_event_resource() → false) and rule-minutes payload shape across all compute_rule_minutes() branches.
Fixed
- Indexed availability: RRULE expansion follows range_type (same as dynamic); orphan rrule on non-rrule rules uses the non-RRULE path and purges legacy expand rows on background passes.
- Google Calendar: inbound sync clears rrule when an event is stored as non-recurring (custom / custom:daterange).
5.4.1 16 Jul 2026 5 improved5 fixed
Improved
- PHPStan: level 2 with a clean baseline - expanded plugin and integration stubs plus PHPDoc/type fixes across core.
- Indexed availability: occupancy reads from appointment cache when healthy (live fallback during backfill; in-cart recurring holds merged); backfill batches gap-only missing appointments.
- Storefront calendar: data-engine reports rules/occupancy source (1 indexed, 2 dynamic, 3 indexed + appointment cache).
- Orders: get_appointment_ids_from_order_and_item_id() deprecated - use get_appointment_ids_from_order_item_id( $order_item_id, true, $order_id ).
- Orders: get_appointment_ids_from_order_item_id() triggers wc_doing_it_wrong when $order_id is omitted (except opt-in order-line _appointment_id cache).
Fixed
- Indexed availability: weekday time:N rules count only matching days; re-enable no longer wipes inline indexing; backfill pending flag clears/re-queues stale jobs and stops on zero-gap batches.
- Google Calendar: scheduled sync is forward-only unless admin full re-sync; inbound sync skips stale events (event ID / site_url mismatch).
- Availability / slots: overlap day keys use Fake UTC civil midnight so 00:00 wall slots stay on the correct day on negative-offset stores.
- Orders: order-scoped line-item lookup ($order_id on get_appointment_ids_from_order_item_id) - orphaned _appointment_order_item_id no longer shows another customer’s appointment; line delete clears the link before ID reuse.
- Cart: session load removes holds tied to terminal orders or past start dates and releases in-cart appointments to was-in-cart.
5.4.0 10 Jul 2026 5 improved20 fixed
Improved
- Settings → Indexing: combined section intro with scannable structure (benefit, when to use, warning, maintenance).
- Pricing: display-only quotes can skip is_appointable() in calculate_appointment_cost() (filter/post flag); add-to-cart still validates.
- Recurring package footer: single cost summary, session count under hero price, deposits excluded from preview; removed deprecated checkout_footer_html.
- Removed ~1 MB of unused Moment.js from storefront scripts.
- PHPStan: third-party and WP-CLI stubs, admin include @var docblocks, Sprint 5-6 cleanup - baseline cleared (0 suppressed errors).
Fixed
- REST API: allowlist availability filter column keys before SQL (closes unauthenticated injection on public /availabilities).
- Storefront timezone Default: persists via appointments_use_site_timezone; custom zones still use appointments_time_zone.
- Storefront: unavailable ?time=HHMM shows selected time with "(Selected time is unavailable)" instead of auto-picking first slot.
- Availability / slots: past-date guard and legacy timed storage use Fake UTC civil day; day/month add-to-cart omits _time after midnight.
- Availability rules: fix rule id assignment and $rule_val init; priority input fills column on global and staff tables.
- Pricing: multi-day day-unit totals sum each civil day (matches slot preview; PAO duration flats skipped when billed per day).
- Per-day inventory: in-cart recurring session_starts occupancy included when marking civil days sold out.
- Admin add appointment: reject missing posted date before parsing year/month/day.
- Admin panel: create panel anchors to calendar slot when Add New is opened first, then a time is picked.
- Admin product staff panel: fix assign-staff AJAX scope and staff view self:: call.
- Admin calendar: preload spans prev/current/next month grids so view switches use cached appointments.
- Google Calendar: sync errors log exception messages; HTTP backoff returns explicit error when retry loop exits without a response.
- Partial refunds: each reduced-qty appointment keeps the correct per-line-item $remaining_qty.
- Series My Account: invalid action redirect no longer leaves $result / $notice undefined.
- Helpers: remove undefined $gmt_offset / $cart_item reads in timezone and addon duration helpers.
- Privacy: exporter registration returns exporters array from register_exporters().
- CRM integration: initialize note strings before appending comment HTML.
- Webtomizer deposits: check $appointment_ids array before updating partial-payment status.
- Product Vendors integration: declare global $wp_roles before role cap setup.
- Follow-Up Emails: pass null appointment to variable-replacement filter when queue item has no appointment context.
5.4.0-beta6 28 Jun 2026 9 improved9 fixed
Improved
- Minimum WordPress raised to 5.9 (Requires at least) so str_starts_with() is polyfilled by core on PHP 7.4; aligns the declared requirement with the existing Requires PHP: 7.4 matrix.
- Recurring: add-to-cart preview cache and request helpers on WC_Appointment_Series_Request (cart trait and integrations call it directly).
- Recurring storefront: per-occurrence reschedule suggests nearest available slots across a frequency-aware window (one RRULE step before and after the target day) instead of a fixed same-day-only 3-day span, so weekly/monthly series now offer adjacent same-weekday/week slots; the reschedule action is a labeled pill (not an icon-only glyph) and the alternatives list is headed "Nearest available s...
- Recurring: appointments enabled by default (wc_appointments_recurring_enabled); disable site-wide with add_filter( 'wc_appointments_recurring_enabled', 'return_false' ); when needed.
- Recurring pay-first: balance order auto-create and invoice filters live on WC_Appointment_Series::auto_create_balance_order_if_due() (removed unused wc_appointment_series_pay_first_created action and Payment pass-through helpers).
- PHPUnit: guard against static calls on trait names (PHP 8.1+ deprecation while Requires PHP is 7.4).
- PHPUnit: DaysPricingTimezoneLiveTest - day-of-week pricing on Fake UTC day slots vs non-UTC store timezone.
- PHPUnit: TimezoneAxisLiveTest - Fake UTC vs site-TZ axis regressions with America/Chicago probe (Rule_Manager, padding date() keys, legacy hourly pricing, day pricing); store-native parity tests retained.
- Recurring: WC_Appointment_Series_RRule::interval_days() is the single freq/interval parser (replaces duplicated parsing in the expansion-range estimator and the suggestion window).
Fixed
- Checkout validation: per-day inventory no longer counts the customer’s own in-cart hold when re-checking availability (same overlap-exclude scope as slot occupancy), fixing false “slot no longer available” at checkout on qty-per-day products.
- Storefront: [appointment+_form] shortcode and Appointment Form block post add-to-cart to the embedding page (same as WooCommerce [product_page]) so product permalink redirects no longer drop booking data.
- Recurring storefront: frequency and payment chips stack in a single left-aligned column below 36em (same breakpoint as Every/For).
- Membership credits: recurring packages no longer emit PHP 8.1+ deprecation notices on add-to-cart (subscriptions integration no longer calls static trait methods).
- Recurring pay-first: balance orders are created when the pay-first series is confirmed at checkout (next sequential order id); opt out with wc_appointments_recurring_auto_create_balance_order.
- Recurring pay-first: balance orders get custom order numbers on create (same hook as other programmatic WooCommerce orders; default pending status from first save).
- Recurring pay-first: balance pay links read the stored payable balance order only (get_balance_pay_url() no longer creates orders on read; staff/customer actions use ensure_payable_balance_order()).
- Recurring pay-first: balance order staff actions on the WooCommerce order edit screen use nonced admin links (no nested forms or JavaScript); HPOS-safe order edit redirect after balance actions.
- Pricing: day/month bookings on the Fake UTC axis set _slot_token_timestamp so range-of-days rules use gmdate weekday (not wp_date), fixing a one-day shift on non-UTC stores (e.g. America/Chicago).
5.4.0-beta5 21 Jun 2026 3 improved11 fixed
Improved
- I18n: merge generic admin confirms, errors, and required-field messages to shared strings; fix post-status _x() context bug; caching horizon months use _n(); align AutomateWoo status labels; normalize All Staff / Export Appointments / Edit product casing.
- Membership credits: View Subscription Credited appointments reuses myaccount/appointments.php (pagination, newest first); credits show as *X of Y remaining*.
- My Account templates: consolidate series list into appointments.php; rename series-detail.php → view-series.php (matches endpoint).
Fixed
- Membership credits: shift stored _wca_credit_balance on linked subscriptions when a plan product's appointments-per-period changes (product save).
- Order lines: get_appointment_ids_from_order_item_id() uses appointment _appointment_order_item_id only by default; order-line _appointment_id cache is opt-in via woocommerce_appointments_use_order_item_appointment_cache.
- Membership credits: consume on payment or when a paid order links an appointment; skip lines with no linked appointment.
- Storefront: Product Add-ons show again after a time slot is selected - revert 5.4.0 show/hide to wc-appointments-appointment-hook-after (matches CSS and theme overrides); remove unused wc-appointments-appointment-extra class.
- Storefront: recurring Repeat this appointment checkbox shows again after slot selection - toggle .wc-appointments-recurring-hook with the same cost-calculation show/hide as add-ons (without restoring wc-appointments-appointment-extra).
- Storefront: recurring series price quote updates when WooCommerce Deposits pay-deposit / pay-in-full (or plan) options change.
- Admin panel: Add New opens the creation panel again - only defer visibility for calendar-anchored create; restore preview fallback dropped in 5.4.0-beta4.
- Google Calendar: disconnect clears sync tokens and cached access tokens; connected UI follows refresh token; upgrade removes legacy pre-5.0 gCal transients; global inbound sync no longer skipped when staff sync for the same calendar failed; 404 on calendar fetch clears sync token and surfaces re-select calendar.
- Recurring pay-first: balance orders are payable only when pending/failed (no read-time line sync or status reset); series balance settles on partial-payment as well as processing/completed; due occurrences marked paid when the balance order is paid online.
- Recurring storefront: order-received and My Account use the compact balance block again - email balance table stays on emails only.
- Recurring pay-first: balance order emails and order views label only the first session as paid on the original order; later sessions show balance-due/confirmed labels; My Account series detail lists original and balance orders; pay-balance link on the order line series display (processing email uses the same order instance as series creation); balance payment links due occurrences to the balanc...
5.4.0-beta4 16 Jun 2026 1 new10 improved5 fixed
New
- Recurring packages: Product Add-ons per add-on Use once per recurring series (default: price and duration on every session); series preview, cart, pay-first, admin panel create, and Add-ons product tab.
Improved
- AutomateWoo: verified compatibility with 6.4.0; appointment time variables use AutomateWoo core AbstractTime (removed duplicate).
- Recurring: scoped reschedule (Apply to: this appointment, following, or all) in admin panel and My Account; fixes series datetime edits that bypassed EXDATE/RDATE. My Account skip/cancel routes through the same scope layer as admin status changes. Batch reschedule conflict errors name the blocking occurrence.
- Recurring pay-first: balance payment request email reuses the shared balance-summary block and package-focused intro (paid-on-original-order and package total above the order table) instead of generic “order created” copy.
- Admin panel: pay-first Package balance section reuses pricing Cost/Tax row styling; bordered action chips and icon-only feedback.
- Membership credits: booking-form picker applies credit at add-to-cart (single get_booking_context() resolver); checkout is confirm-only with compact fallback opt-in.
- Membership credits: cart shows strikethrough covered value with $0 due; membership credit row shows plan, covered amount, and balance (orders too); credit lines stay $0 after other cart pricing hooks.
- Membership credits: checkout opt-in uses the same markup and styles as the booking form footer.
- Membership: subscribe thank-you Book appointment CTA; _wca_first_booking_product_id stored at grant time.
- My Account → Appointments: compact membership summary card (replaces full credits table); empty-state Book links to stored first-booking URL when available.
- Settings → Appointments: removed Recurring packages section; auto balance order and customer invoice timing ship as recommended defaults (auto-create on, manual invoice), overridable via wc_appointments_recurring_auto_create_balance_order and wc_appointments_recurring_balance_invoice_timing filters.
Fixed
- Recurring storefront: package preview via wc-ajax (shared WooCommerce session) - no stale REST nonce / “Cookie check failed”; preview stays public like slots AJAX.
- Recurring pay-first: repair incomplete pay-first meta when series totals are missing on the series row.
- Admin panel: recurring series orders use the preview package total - set unit price before add_product() so the order line is correct at creation.
- My Account: cancelling a recurring appointment from the appointments list now updates the series (EXDATE) instead of orphaning the occurrence.
- Membership credits: recurring packages can use membership credits - one credit per billable appointment in the series (cart $0, validate balance, consume/restore on order); cart and booking UI show remaining credits after cart reservations.
5.4.0-beta3 9 Jun 2026 2 improved8 fixed
Improved
- Recurring pay-first: balance orders as appointable line items with in-item series summary (legacy fee orders upgraded on repair); mirror parent balance block and occurrence labels.
- Admin panel: preset last-used appointment status on create.
Fixed
- Recurring storefront: qty/add-on/payment refresh pricing only; balanced reschedule suggestions; Fake UTC session_starts for TZ-aligned cart/checkout; guest preview no login; no double-charge when inventory > 1.
- Recurring pay-first: Mark paid offline completes balance order; balance admin mirrors parent block; parent qty/tax/shipping before totals; incl-tax catalog as ex-tax nets; materialization no longer overwrites line subtotals.
- Recurring / emails & My Account: View series 404 - register view-series query var; permalink flush.
- Recurring series: occurrences inherit checkout inventory/qty at create.
- Recurring / product admin: Repeat frequency checkboxes no longer overlap (plugin admin CSS).
- Admin panel: view/edit qty from calendar inventory; create form browse-on-focus, debounce, day-slots spinner, auto-pick success.
- AutomateWoo: appointable type on product_type_selector outside wp-admin for Action Scheduler product queries.
- Admin calendar: live SSE storefront updates via admin-ajax (EventSource cannot auth REST cookies).
5.4.0-beta2 4 Jun 2026 5 improved5 fixed
Improved
- Recurring & membership: shorter customer-facing copy; package/series for bundles, appointment (not session) for counted slots; email intros consolidated.
- Availability rules: DB 5.4.4 indexes on event_id, parent_event_id, (kind, kind_id); base-table column filters for Google Calendar and product/staff rule loads.
- Recurring storefront: preview/cost loading bars, dimmed list/price, Book now disabled while preview runs or after errors.
- Recurring: format_session_time and admin customer TZ line on WC_Appointment_Series_Email.
- Block emails: missing ##WOO_CONTENT## falls back to classic PHP template from the sending WC_Email.
Fixed
- Recurring / emails & orders: one Appointment Series created email per order (all series in one table); line _wca_recurring / _wca_series_id only on mixed checkout; skip non-series occurrence ids; session TZ short name when booking TZ ≠ site.
- Recurring storefront: booking-axis RRULE expansion and Fake UTC preview labels; store-axis conflict checks; orders/emails use format_session_time().
- Recurring: REST idempotency cleanup; pay-first totals backfill on create; parent _wca_series_id only when empty; materializer optimistic-lock retry; Fake-UTC store-wall "future" scope cutoff.
- My Account: cancel/reschedule on wp_loaded; recurring in-cart occupancy after wp_loaded.
- WooCommerce Subscriptions: renewal credits once on woocommerce_subscription_renewal_payment_complete (no double grant); restore clamped to period allotment when rollover is off.
5.4.0-beta 2 Jun 2026 4 new11 improved7 fixed
New
- Recurring prepaid packages: series checkout (RRULE, REST v2 /series, storefront, cart, My Account) with min/max sessions, frequencies, and partial checkout when some sessions are unavailable.
- Recurring pay-first: first session at checkout, balance order for remainder (product options, Settings → Recurring packages, Pay remaining balance on storefront and My Account).
- Recurring package pricing: Sessions in package rule on series subtotal (per-session adjust; ×/÷/= on package total).
- WooCommerce Subscriptions membership plans: credits per billing period, eligible appointable products, $0 credit checkout.
Improved
- Settings → Appointments: Recurring packages (auto balance order, customer invoice timing).
- Recurring / product admin: recurring flags, frequencies, payment model (wc_appointments_recurring_default_payment_model), min/max sessions, billable minimum.
- Admin panel: recurring badge, sessions panel, scoped Apply to, pay-first pricing, balance actions, list filter, calendar ↻ preload; subscription Appointments tab and Eligible products.
- Storefront: Repeat picker with live preview; prepaid/pay-first totals; hides Deposits during recurring checkout.
- Cart & checkout: recurring lines, pay-first first charge, virtual occupancy, one membership credit per order at checkout.
- My Account: series list/detail (cancel, skip, reschedule, pay balance), standard tables, Series column, series_details; membership credit balance.
- Emails / recurring: Appointment Series (prepaid created, bulk scoped); balance and schedule on orders, thank-you, and invoice.
- Recurring: cached preview, batched recurrence, single-query admin counts, lightweight REST preview, one-SQL panel /occurrences.
- Recurring foundation: series tables, WC_Data store, manager/preview/materializer, REST v2, cart trait, storefront JS, My Account, WCS module, CLI, tests; DB 5.4.1-5.4.2.
- Recurring layout: meta on WC_Product_Appointment; consolidated WCS membership; series block emails via woocommerce/email-content.
- Recurring series object cache off by default (wc_appointments_series_cache_enabled).
Fixed
- Recurring / pay-first balance: wp_wc_appointment_seriesmeta, HPOS staff links, calculate_balance_due_display(), prepaid option match, first occurrence on parent until paid.
- Recurring / storefront & REST: RRULE COUNT/UNTIL, Fake UTC labels and session_starts, cart pricing on init, /occurrences meta and scoped following, PHP 8 REST visibility.
- Admin calendar: ↻ prefix in eventDataTransform; series_id from _wca_series_id on preload.
- Recurring / emails & orders: block appointment_series emails; consolidated occurrence suppression; no duplicate mail after materialize or bulk scope.
- WooCommerce Subscriptions: no woocommerce_is_subscription recursion via appointable get_price().
- My Account view-series: cancel/reschedule per product on completed prepaid series; POST permission checks; occurrence reschedule via standard form (reschedule_one).
- Emails & My Account: block ##WOO_CONTENT## classic fallback; reschedule notes admin-only; skip-one suppresses order sync (prepaid order cancels when all appointments cancelled).
5.3.11 9 Jun 2026 3 fixed
Fixed
- Twilio SMS integration: restore hook registration removed in 5.1.0 (Appointments SMS settings, scheduling, and notifications with WooCommerce Twilio SMS Notifications 1.12+); {appointment_date} / time tokens use get_start_date() / get_end_date() so all-day civil dates match emails and order summary on non-UTC stores.
- All-day (day/month duration) appointments after checkout: order summary, emails, and My Account show the same civil date as cart/checkout - get_start_date() / get_end_date() and email formatters use Fake UTC civil digits (gmdate + date_i18n) instead of wp_date() on stored midnight tokens, which shifted the calendar day on non-UTC site timezones.
- All-day bookings: _local_timezone is written only for timed products in wc_appointments_get_posted_data(); WC_Appointment::set_all_day() / set_local_timezone() and the data store drop customer TZ meta on all-day saves - civil dates stay timezone-agnostic without per-caller guards.
5.3.10 2 Jun 2026 6 fixed
Fixed
- Storefront appointment cost quote when catalog prices are entered inclusive of tax: footer HTML uses the same display amount as AJAX raw_price (not tax-stripped line_net), so logged-out customers see the correct gross total.
- Availability / add-to-cart: day- and month-unit checks use Fake UTC civil tokens (gmdate / shared slot-token helpers) for past-date guards, span end calculation, and day-slot capacity keys - not wp_date() / site-TZ DateTime on the posted timestamp - so same-day bookings and slot lookup stay aligned on non-UTC stores (e.g. America/Vancouver).
- Storefront calendar (no staff preference, day/month + staff): multi-staff indexed slot generation no longer runs the minute-mask loop on day/month products - use per-staff civil-day slots and union merge instead; no-preference day summary uses per-staff OR (max), not summed slot counts across merged fragments.
- Admin panel create: day/month availability check and save use Fake UTC civil midnights for date/month ranges (matches storefront slots and get_day_civil_span_capacity keys); admin month calendar bounds for day/month products use the same civil UTC axis.
- Is_appointable() past-date guard for day/month products uses wc_appointments_is_past_request_date() (Fake UTC civil axis) instead of date() on _date, matching availability checks.
- Admin panel create success message formats saved day/month ranges from appointment timestamps via wp_date() (not strtotime() on date strings).
5.3.9 25 May 2026 3 improved14 fixed
Improved
- Improved admin panel styling with bigger input selection.
- Admin panel create/edit: datetime row shows a loading state while product details, first-available auto-pick, day slots, or availability AJAX run; success/error only after the check completes.
- Admin panel create: first-available auto-pick scans the bookable horizon in 14-day chunks and stops at the first slot instead of loading the full range up front.
Fixed
- Admin panel create: availability no longer flashes loading twice or clears to blank before first-available prefill; date-only calendar prefill waits for day slots before checking; browse does not re-open after the user closes it during the same unavailable episode.
- Admin panel: datetime loading no longer sticks when day-slot AJAX fails or returns empty, when a superseded availability request aborts, or while bootstrap waits only on an in-flight slots request (not missing cached data).
- Slots / day & month products: timed global RRULE blocks (e.g. weekly Tue 4-6pm) block the whole civil day again - date-level checks use RRULE occurrence expansion, not midnight-only overlap, so higher-priority global rules override product day rules (RuleMinutesRruleCivilDayTest).
- Admin panel browse: slot chips show remaining capacity (e.g. 1 available) on multi-capacity products when only one place is left, not only when two or more remain.
- Slots: dynamic availability (indexing off) paints full half-open unappointable ranges on the minute mask - priority blocks no longer leave appointable boundary minutes (e.g. 10:00-11:00 block showing 10:59 slots); matches indexed mask painting.
- Slots / calendars: minute/hour bookings that start at site midnight (including long durations such as 12 hours) block capacity again - timed overlap day-keys use Fake UTC site civil buckets for both occupancy indexing and slot lookup instead of mixing wp_date() site midnight with the civil UTC-midnight fast path.
- Slots / calendars: multi-day day-unit occupancy for admin-created (site-local midnight) bookings - civil overlap keys use the site calendar date (Fake UTC) so every booked day blocks correctly (e.g. June 4-5 for a 2-day booking starting June 4); wc_appointments_get_posted_data() stores matching Fake UTC start/end for new day/month appointments.
- Slots / calendars: multi-day day-unit products block every booked civil day again - occupancy uses live appointments; overlap day-keys use timezone-agnostic civil dates (gmdate on stored tokens), matching slot tokens, not wp_date() site-local midnight.
- Admin panel create: first available date/time via one AJAX call (WC_Appointment_Slots_Provider::get_first_available_slot, same as storefront), including when the first slot is outside the current month; calendar day click/drag date-only prefill auto-picks the first time or opens browse once with noSlotsOnDateSelect when that day has no slots; browse auto-opens only on unavailable validation (no...
- Slots / calendars: minute/hour day availability (storefront day_data, admin mini-calendar, time-slot lists) counts only bookable slot starts via shared WC_Appointment_Slots_Collection::slots_for_day_summary() on the same get_collection() engine - fully booked days no longer stay selectable when only sub-duration engine segments remain.
- Slots / time picker: Availability check → The starting slot only no longer lists grid starts in appointable fragments shorter than the booking duration (phantom evening slots such as 19:00 on 12h/12h products when the index leaves a tail after a gap); full-day ranges still allow starts when duration exceeds the window (e.g. 25h in 24h).
- Checkout validation (WC_Appointment_Cart_Manager::validate_appointment_order): re-validates cart slots via WC_Appointments_Controller::get_indexed_total_available_for_range() (same slot_matches_requested_range tolerance as add-to-cart) instead of strict start_ts equality; all staff together uses staff id 0 fan-out like is_appointable(); in-cart holds are excluded from overlap during the check.
- Staff all together: cart booking no longer collapses multi-staff _staff_ids to a single _staff_id during WC_Appointment_Booking_Handler::create_appointment() availability merge.
- Admin panel: WooCommerce Deposits on create - calculate_costs / create_appointment honour deposit choice and tax display (incl./excl. catalog); deposit footer quote and order item meta (_deposit_full_amount, etc.) match storefront; pay-deposit hides footer price edit (product deposit rules); pay-in-full edits full service price via raw_full_price; mistaken deposit-only overrides corrected on save.
5.3.9-beta 20 May 2026 12 improved26 fixed2 removed1 changed
Improved
- Unified day_data: storefront calendar is day_data-only - per-day padding / restricted, full per-staff occupancy fields, redundant display_day_summaries removed; to_day_data() optional fold-in; availability widget/block use get_day_data() with selectable; resolve_schedule_bounds() maps Unix 0 to the booking horizon.
- Padding and fully-booked month aggregation call get_padding_days_for_product(), get_padding_days_for_fully_booked_dates(), and get_scheduled_month_slots() (get_day_data()-based).
- Storefront date-picker.js: removed legacy four-array branch and is_slot_appointable; removed unused getTzOffsetFromString / getCustomerTzOffsetMinutes (offset still server-supplied on AJAX).
- Get_rest_records() date-range mapping uses wp_date() / wp_timezone() instead of date() / strtotime().
- WPML / WCML: admin appointment panel create - independent multicurrency selector (integrationCurrency on wc_appointments_admin_panel_config); appointment-currency cookie + wcml_client_currency on panel cost, add-on, and set-currency AJAX; WCML loads in admin for those actions; rate conversion via pricing-rule filters, wc_price_args, and WCML get_product_addons_fields (add-ons refetch on currenc...
- WooCommerce Payments Multi-Currency: admin appointment panel create - independent multicurrency selector (same integrationCurrency pattern as WCML); appointment-currency cookie + wcpay_multi_currency_override_selected_currency filter for exchange rate conversion on panel cost AJAX; addon prices converted via woocommerce_product_addons_*_price_raw (WC Payments skips admin); _order_currency persi...
- Admin appointment panel create: WooCommerce Deposits - deposit / pay-in-full / plan UI (storefront POST keys), line totals and meta on wc_appointments_create_appointment, _wc_deposits_version when applicable; product AJAX exposes wc_deposits; compact amounts in wca-slots-header / wca-slots-count.
- Admin panel browse: time slot chips use a four-column equal-width grid (.wca-slots-grid / .wca-slot-chip in appointment-admin-panel.scss).
- Google Calendar OAuth (settings + staff profile): Google Identity branding - Continue with Google, assets/images/google-g-mark.svg, shared markup via WC_Appointments_GCal::get_google_oauth_connect_button_html().
- Admin panel: woocommerce_appointments_add_appointment_page_redirect gains a 5th string $context (legacy_post, admin_ajax_standalone, admin_ajax_calendar); full admin calendar never returns post_create_redirect (callbacks may still read context).
- Update translations (+7 new strings, -2 strings removed).
- Settings → Indexing: during manual re-index, an admin notice explains the storefront uses dynamic availability checks until rebuild finishes (availability stays correct; performance may be slower during rebuild).
Fixed
- Storefront auto-select with woocommerce_appointments_include_sold_out: slots AJAX picks the first bookable time from the availability collection (marks it selected in HTML); the time picker applies that server selection instead of the first listed slot when sold-out rows are shown.
- Auto-select slot highlighting: match engine start_ts directly and compare unified display_time with gmdate() on Fake UTC timestamps so wp_date() does not shift the selected row (e.g. 10:00 marked as 12:00).
- Storefront time picker: rapid date changes with auto-select no longer apply a stale slots AJAX response (wrong time/timestamp for the selected day); cost calculation ignores superseded responses before enabling add to cart.
- Storefront time picker: multiple appointment forms on one page (shortcodes/blocks) scope slots, autoselect, and date-selected per .wc-appointments-appointment-form widget again.
- Storefront cost calculation: multiple appointment forms on one page no longer share one AJAX slot via form.index() (earlier forms were left loading while only the last form showed a price).
- CSV appointment export Cost: linked order lines use WC_Order::get_line_total( $item, true ) (after discounts, incl. tax) instead of stored _appointment_cost; appointments without a line still use stored cost.
- Appointment export Products filter: woocommerce_json_search_appointable_products returns plain-text labels (fixes raw HTML in Select2); admin product lists keep HTML labels.
- All-day civil dates: WC_Appointment::get_start_date() / get_end_date() and all-day email branches use wp_date() so local-midnight UTC storage does not show the wrong calendar day vs other wp_date()-based flows.
- All-day reschedule (WC_Appointment_Form_Handler): persists posted _start_date / _end_date, sets all_day, clears local_timezone, skips form timezone metadata. New bookings (WC_Appointment_Booking_Handler): all-day day/month products skip the legacy strtotime() pass so instants match the posted-data pipeline (night bookings unchanged).
- Settings → Appointments global availability: synced Google Calendar rows (#availability_rows_synced) merge into submit JSON with site rules so reorder/ordering persist; rrule rows expose data-wca-rrule and include rrule for is_rule_valid on save.
- Admin appointment panel create: manual price override when prices are entered inclusive of tax no longer double-applies tax-inclusive→net conversion; saved order line matches the edited gross price.
- Admin day slots: with woocommerce_appointments_slots_display_pricing, price.display matches storefront serialize_slots() (staff-aware default; / only when WC_Appointment_Slots_Pricing::is_slot_price_discounted()).
- Restrict start days: WC_Product_Appointment::is_appointable() weekday uses the civil posted _date with UTC noon + gmdate( 'w' ) (Gregorian, TZ-agnostic; matches day slot / Fake UTC digits); falls back to gmdate( 'w', _start_date ) if _date is missing or unparsable. Avoids date( 'w', … ) under PHP default UTC wrongly rejecting allowed starts.
- Completed appointments now persist customer status as arrived, so _appointment_customer_status and CSV appointment exports no longer remain expected after automatic completion.
- Admin panel create billing: restored Country / Region and State / County (wcaPanel.billingCountries / billingStates); country, state, and payment s use the same selectWoo styling as other panel dropdowns.
- Product Add-Ons (bundled): Multiple choice dropdown and Images swatch durations no longer run through absint(), so negative per-option durations match checkbox/radio and data-raw-duration stays signed.
- Appointment emails with multiple recipients now keep one visible recipient and move remaining addresses to BCC, so customer/staff addresses are not shared in the To list.
- WPML / WCML: get_post_metadata filter no longer calls woocommerce_wpml::$products when WCML skipped full init (dependency failure leaves products unset), avoiding a fatal error.
- WPML / slots: wc_apointments_check_appointment_product now receives the result of wc_appointments_check_appointment_product so WCML's "same translation" false is not overwritten; cross-language capacity uses the booked quantity instead of forcing the full product capacity.
- Admin panel create WCML: currency selectWoo applies every selection (no duplicate React onChange / controlled value conflict); product add-ons load after currency change without a panelAddonsCfg reference error.
- Admin appointment panel create: footer price shows the calculated total again (server HTML when there is no manual override); regression from gating display on multicurrency integration only.
- Unified slots (dynamic / indexing off): WC_Appointment_Slots_Rule_Minutes::minute_range_from_times() now paints time windows half-open [from, to) to match indexed minute-mask painting (m < end_min). Fixes extra grid starts at the rule end boundary (e.g. Saturday 9:00-10:00 showing 10:00) when availability indexing is disabled.
- Storefront calendar/indexing: indexed slot reads require a ready availability index and fall back to the dynamic engine when the cache is empty, stale, still catching up after re-enable, or bypassed after a failed index read, so calendars stay accurate while background indexing runs.
- Settings → Indexing: manual re-index cancels pending background index Action Scheduler jobs and blocks duplicate scheduling until rebuild completes; availability rules saved while indexing is off queue for indexing on re-enable; initial backfill is scheduled at most once.
- Settings → Indexing: manual re-index handles failures and timeouts safely (failed/stopped status, stale lock cleanup, Stop/Resume controls, clear notices) so admins are never stuck on a spinning re-index and orphaned manual_reindex flags do not affect the storefront after an interrupted run.
- Product Custom availability rules: clearing Capacity (inventory) on save now resets the rule qty; omitted JSON qty no longer leaves the previous value in the database.
Removed
- Breaking change - Removed WC_Appointments_Controller::find_padding_day_slots(), get_padding_day_slots_for_scheduled_days(), and find_scheduled_month_slots(). Use WC_Appointment_Slots_Provider::get_padding_days_for_product(), get_padding_days_for_fully_booked_dates(), and get_scheduled_month_slots().
- Breaking change - Removed WC_Appointments_Controller::find_scheduled_day_slots(), WC_Appointment_Slots_Provider::get_scheduled_day_slots(), and WC_Appointment_Slots_Collection::legacy_scheduled_arrays_from_day_data(). Use get_day_data() / to_day_data(); padding anchors from fully booked day_data (get_padding_days_for_fully_booked_dates(), Y-n-j keys); months via get_day_data(..., 'Y-n'). woocom...
Changed
- Breaking change - Calendar AJAX wc_appointments_find_scheduled_day_slots no longer returns legacy keys (partially_scheduled_days, remaining_scheduled_days, fully_scheduled_days, unavailable_days, padding_days, restricted_days) or invokes that filter at the AJAX layer. Read day_data / meta, filter the JSON response, or use get_day_data() in PHP where the filter still applies.
5.3.8 8 May 2026 1 new9 improved11 fixed
New
- WooCommerce block email editor support for appointment transactional emails (synced block templates under templates/emails/block/, hook-driven parts under templates/emails/parts/, personalization tags, preview context, admin styles).
Improved
- Date picker default date uses WC_Appointment_Slots_Provider::get_first_available_slot() which leverages Collection day summaries to skip unavailable days. Complexity reduced from O(all_slots) to O(days + slots_in_first_available_day).
- WC_Product_Appointment::get_first_available_slot() and get_slots_in_range_lazy() (and private get_slots_lazy_by_duration / get_slots_in_range_for_*_lazy helpers) removed; use WC_Appointment_Slots_Provider::get_first_available_slot().
- Removed deprecated dead code with no external calls: 11 methods from WC_Appointments_Controller (deprecated 2.4.9-4.7.0), 5 functions from wc-appointments-functions.php (deprecated 1.15.0-5.1.0), and 3 methods from WC_Appointment (deprecated 4.2.0-5.3.8). Deprecated code still in use internally was retained.
- Removed deprecated dead code from WC_Product_Appointment: get_time_slots(), get_time_slots_html(), get_slots_availability(), get_all_staff_availability(), get_min_timestamp_for_date(), get_available_appointments(), get_appointments_in_date_range(), get_unavailable_minutes(), get_minutes_from_rules(), get_available_slots(), get_slots_in_range(), get_slots_in_range_for_day(), get_slots_in_range_f...
- Removed deprecated dead code from WC_Appointment_Slots_Legacy_Adapter: get_time_slots(), get_available_slots()
- Admin appointment panel (wcaPanel): localized incl./excl. tax short labels from WooCommerce; footer cost hint and add-on section respect wc_prices_include_tax() and whether tax is enabled; AJAX posts (wc_appointments_panel_cost_excl_tax, add-on incl./excl. flags, override excl. flag) only when the catalog is exclusive so net/gross semantics stay consistent.
- Product Add-Ons integration: panel AJAX uses filters so admin add-on price display and suffix follow the same inclusive/exclusive catalog basis as the appointment panel.
- Admin appointment block templates: admin edit link uses the same printf( esc_html( … ), '' ) pattern as WooCommerce core block emails (e.g. store email / customer name in default content).
- Update translations (+19 new strings).
Fixed
- Admin product Custom availability rows (html-appointment-availability-fields.php): date/time display no longer converts using get_staff_timezone( get_current_user_id() ), which applied the viewing user’s saved appointment timezone to everyone (including administrators). Uses WC_Appointments_Timezone_Helper::get_admin_user_timezone() so store admins and shop managers see site wall times; staff-o...
- Admin user profiles now show a single appointment timezone selector for all users. Staff profiles use the shared field while keeping the staff profile navigation section and enhanced searchable timezone picker.
- Indexed availability: manual Re-index no longer raises per-rule limits so high (e.g. thousands of RRULE occurrences or a full year of days in one pass) that a single AJAX chunk could hit browser/proxy timeouts despite batching. WC_Appointments_Cache_Availability::run_rule_index_pass() uses modest multiples of the normal per-pass caps; rules that still need more work are re-scheduled as before.
- Unified slots when a specific staff is selected: WC_Appointment_Slots_Rule_Resolver::build_minute_mask (dynamic rules) and WC_Appointment_Slots_Data_Normalizer::build_minute_mask_from_index_rows (indexed cache) now apply matching staff-scope rows inline on the main minute mask in sorted override order, instead of replacing the mask with a staff snapshot that could resurrect minutes a higher-pri...
- Indexed full-day row types days / months / weeks: minute-mask painting clamps the day span at 1440 minutes (end of civil day) so the mask tail past midnight is not left appointable from index timestamps; avoids rule-blocked calendar days appearing clickable when availability indexing is enabled.
- Legacy storefront calendar payload (WC_Appointment_Slots_Legacy_Adapter::collection_to_scheduled_days): days with unavailable status are also copied into fully_scheduled_days for the same date keys, because date-picker.js treats fully_scheduled_days as non-clickable; rule-only unavailability no longer leaves those days selectable when the modern day_data path already marks them closed.
- Admin appointment panel: create success schedule line uses store date/time formats (wc_appointments_date_format / wc_appointments_time_format); when the acting user’s admin timezone differs from the site, text is derived from start/end timestamps via wc_appointment_timezone_locale so it matches the times they picked. Timezone context uses abbr.wc-appointments-tz-abbr beside the line (same patte...
- Admin appointment panel edit mode: footer no longer shows a live-calculated price (slot/product rules); it could disagree with view-mode order line totals, and cost is not editable there. View mode still shows pricing from the order.
- Admin appointment panel: create submit sends wc_appointments_field_start_date_timestamp when a slot is selected so server cost matches the footer (fixes line total 0 when the price field was never focused). Order line total always adds Product Add-Ons to the appointment base; price override prefills/commit uses appointment base only (footer raw minus addon cost) so add-ons still apply after tou...
- REST GET wc-appointments/v2/appointments/{id}: embedded order_info now matches calendar batch order payloads - each line item includes subtotal_tax / total_tax, and the order includes total_tax, discount_tax, and coupon_lines. The admin appointment view panel Cost/Tax row uses these fields on first open; previously they were omitted so Tax could appear only after closing and reopening (calendar...
- Admin manual appointment creation when WooCommerce prices are entered inclusive of tax: AJAX cost display and order line totals align with the inclusive/exclusive catalog basis (appointment base + Product Add-Ons as one tax-aware bundle on the line so calculate_totals() does not stack VAT on top of tax-inclusive amounts).
5.3.7 29 Apr 2026 4 improved11 fixed
Improved
- Admin calendar: on expired nonce/session (REST 401/403), show an expiry banner with reload link instead of failing silently after long idle periods.
- Admin appointment panel: create/edit success step uses a light success-tinted circular badge for the checkmark (aligned with other success affordances), not a plain large icon.
- Update translations (+2 strings, −11 strings removed).
- Removed deprecated per-product customer_timezones; timezone support is site-wide. has_timezones() now reads wc_appointments_timezone_support_enabled (default true). Removed get_customer_timezones / set_customer_timezones, related data store mapping, admin and REST fields, and WCML/Polylang sync for that setting.
Fixed
- Admin timezone display: appointments_time_zone cookie no longer applies in admin/REST. Only staff (providers) may use profile timezone in admin; admins and shop managers always see site timezone. Remote staff can still set profile timezone.
- Admin create flow, existing order: order number no longer appears twice in Select2 and the selected chip - both prefixed # + internal ID onto labels that already included the display order number.
- Legacy calendar staff view: staffed appointments no longer also list under Unassigned - $is_creator matched creator_id === 0 because (int) 'unassigned' === 0; creator shortcut now requires non-zero creator_id.
- Manual appointment on existing order (admin AJAX): line meta used public appointment_id, so emails/item meta showed an extra row. New rows use _appointment_id only (checkout parity); formatted order item meta strips legacy public appointment_id for display.
- Admin browse day slots: discounted price.display ( / ) was React-escaped; .wca-slot-price now renders with dangerouslySetInnerHTML (same as panel footer).
- Recurring time:range: same-day windows ending at 00:00 were misclassified as overnight (string order: 00:00 < 08:00, 24:00 < 08:00). WC_Product_Appointment_Rule_Manager / WC_Appointment_Slots_Rule_Minutes now use minute-based wrap helpers (is_wrap_midnight_hhmm_pair / is_wrap_midnight_time_pair); evaluate_rule_for_timestamp shares the same bounds. Availability cache indexing aligns with slot ru...
- Unified slots + woocommerce_appointments_slots_display_pricing: WC_Appointment_Slots_Pricing::compute_slot_price_raw() returns the full filtered slot price when the filter runs (not only when below default). WC_Appointment_Slots_Collection::serialize_slots() writes price_data.display / price_formatted HTML; the lazy formatter had read a non-array price from jsonSerialize(), so strikethrough/sal...
- Availability rules: changing range_type (e.g. Date range with time → Days) clears unused from_date / to_date via apply_json_rule_to_availability() (time:range, custom:daterange, store_availability still keep dates), so weekly/monthly rules are not skewed by stale columns.
- Indexed availability cache: indexer ignores stray from_date / to_date on days, months, weeks (same as recurring time / time:N), so corrupt rows no longer shrink the cache window until re-saved.
- Admin appointment save (WC_Appointment_Meta_Box_Data::meta_box_save): failed start/end validation now sets self::$saved_meta_box before return (matches success path), avoiding duplicate admin errors or extra save_post work in the same request.
- Day-unit products when add-ons (or appointment_form_posted_total_duration) extend past base duration: WC_Appointments_Controller::get_exact_available_capacity_for_slot() no longer uses the multi-day “start row only” shortcut (it skipped later days) or get_extended_range_available_capacity() for that span (UTC civil-day mismatch caused false unavailability). Added get_addon_extended_day_span_cap...
5.3.6.1 24 Apr 2026 1 improved1 fixed
Improved
- What's New admin screen (html-wc-appointments-whats-new.php): admin appointment panel behavior is now described and illustrated clearly - by default the panel anchors beside the selected calendar appointment (prefers right, then left, centers only when neither side fits), with backdrop and roadmap/changelog wording aligned. This replaces misleading “side panel” / generic sidebar copy and SVGs t...
Fixed
- Admin appointment save (WC_Appointment_Meta_Box_Data::meta_box_save): blank or invalid start/end date (or missing time when not all-day) no longer calls mktime() with non-integer parts (PHP 8+ TypeError / fatal). Invalid input shows a WooCommerce admin error and the appointment object is not updated on that request.
