Version 1.3.0

July 26, 2026

{% include 'booked/frontend/wizard' %} now renders the framework-free vanilla wizard by default — no template changes needed. The deprecated Alpine wizard is available for one release window via the legacyWizard setting (or {% include … with { legacyWizard: true } %}), logged as deprecated, and removed in 2.0.

Added

  • Framework-free booking wizard — a zero-runtime-dependency rewrite of the booking flow: a headless state-machine core (BookedWizard.create()) plus a vanilla renderer, replacing Alpine.js. Runs under a strict CSP (no unsafe-eval, no inline executable script — config is read from a JSON block), ~14.5 KB gzipped (core + renderer). See VANILLA_WIZARD.md.
  • Accessible calendar replacing Flatpickr: WAI-ARIA date grid with roving tabindex, full keyboard navigation (arrows / Home-End / PageUp-Down / Enter-Space), month crossing, and a two-click range mode for multi-day services. Month, weekday, and full-date names are localized from the site language via Intl.
  • Booking-management mode (?manage=<token>) — a self-service view to cancel, reduce, or increase the quantity of an existing booking, driven by the reservation's confirmation token.
  • Multi-seat quantity pickers for slots, multi-day ranges, and events (bounded by remaining capacity), with required-extras enforcement.
  • Captcha widget support — Turnstile, hCaptcha, and reCAPTCHA v3, with the token minted/refreshed before submit.
  • Versioned headless API at /booked/api/v1/… — the wizard's REST surface as documented public API, aliased onto the existing controllers so the old paths keep working.
  • Soft-lock hold timer — the wizard now shows and manages the reservation countdown, auto-extends once on submit, and cleanly enters an expired state (new slot/extend-lock action + SoftLockService::extendLock(), capped by softLockMaxLifetimeMinutes).
  • Bring-your-own-frontend — the core ships as a headless ESM/UMD build (booked-wizard-core) drivable with no DOM, for custom/React/Vue frontends.

Parity (vanilla wizard)

  • Full feature parity with the Alpine wizard: service, extras, location, employee, single-slot / fixed-day / flexible-day date selection, multi-seat quantity, customer info, review summary, and Commerce redirect; event-date flow (incl. paid events and per-event quantity); booking management; waitlist branch; honeypot anti-spam.
  • Styling matches the previous wizard — the same editorial black-and-white design (2px borders, square corners, uppercase controls), driven by --booked-* CSS custom properties for theming.
  • The review summary and confirmation screen show only the rows that apply (no empty labels), and prices display with the Commerce currency symbol.

Accessibility

  • role="region" landmark, live-region step announcements, focus moved to each step heading, role="alert" errors, aria-pressed/aria-selected selection state, aria-required on required fields, and full-date accessible names on calendar days.

Internationalization

  • All runtime strings the core emits (announcements, countdown, validation, errors) and the calendar's navigation labels are translatable and shipped in en, de, fr, es, it, ja, nl, and pt.

Version 1.2.1

June 17, 2026

Added

  • MCP authorization settings (Settings → Security): Allow MCP write operations (mcpWriteEnabled) and Allow MCP refunds (mcpAllowRefunds), both off by default. A migration adds the columns to existing installs.

Security

  • MCP write tools are now default-deny: every create/update/cancel/delete/refund tool is gated behind mcpWriteEnabled (and refunds additionally behind mcpAllowRefunds), rather than relying on the craft-mcp server config alone (this supersedes the 1.2.0 "authorization is delegated to the craft-mcp server" note). In a web context an authenticated user must also hold booked-manageBookings.
  • booked_refund_reservation is now idempotent — it nets out prior refunds and never refunds more than the outstanding (paid minus already-refunded) amount.
  • Customer PII (email/phone) is now redacted by default in all MCP responses — including create/update/booking responses, not just bulk lists — so a forgotten flag fails safe.
  • MCP list tools clamp their page size to a hard ceiling (200) to prevent unbounded result sets (memory exhaustion / bulk data export).

Fixed

  • booked_delete_event_date on an event that still has reservations now returns the actionable "retire it with enabled=false instead" message (via a typed exception) rather than a generic internal-error response.

Version 1.2.0

June 17, 2026

Added

  • MCP integration: Booked now registers ~50 tools with the optional craft-mcp plugin, exposing near-complete headless admin to AI assistants — services, employees, locations, schedules, blackout dates, service extras, availability, reservations, event dates, waitlist and reporting. Covers reads, create/update (soft-disable via enabled rather than hard delete), reschedule, quantity changes, refunds and analytics. The dependency is soft (class_exists-guarded) — Booked runs unchanged when craft-mcp is absent. See MCP.md.
  • MCP safety model: customer email/phone are redacted on every reservation/waitlist read (not just bulk lists); booking capability tokens and virtual-meeting URLs are never exposed; cancellations always run the refund/capacity-release flow (status cannot be force-set to cancelled via update); retired (disabled) services/events remain listable and re-enablable; inputs are validated (quantity ≥ 1, employee serviceIds must exist); and notification/refund side effects are rate-limited with a fixed-window, mutex-guarded limiter (separate budgets, charged only on success) since Booked's IP-based limiter does not apply over MCP. Authorization is delegated to the craft-mcp server (IP allowlist / dangerous-tool gating). All 50 tools verified end-to-end against the live MCP server.

Version 1.1.1

April 15, 2026

Added

  • New slot/get-range-capacity controller action and BookedAvailability.getRangeCapacity() JS helper for querying the remaining capacity of a multi-day date range (tightest day wins).
  • ScheduleResolverService::getCapacityForDay() resolves day-based capacity from Schedule.workingHours[day].capacity, honoring service → employee → aggregated-employee precedence.
  • Day-service wizard step now shows a quantity picker (with +/− controls and live remaining-capacity hint) when a multi-day range has capacity greater than one.
  • Inline booking error banner on the date/time step so backend error messages surface to the user instead of being swallowed.

Changed

  • Multi-day availability now enforces capacity per day using the resolved schedule capacity instead of Service.capacity, correctly accounting for overlapping reservations across each day of the range.
  • SlotController rate limits raised to 120 req/min across get-slots, get-dates, get-valid-end-dates, get-event-dates, and get-availability-calendar to reduce false 429s for legitimate wizard traffic.
  • BookedAvailability.getDates() and createBooking() now parse error response bodies so the wizard can display the server message and status code.
  • Wizard caches fetched available-date results per month/service/employee/location/quantity/extras key and de-dupes in-flight requests.
  • Flexible-day price calculation guards against missing selectedService.price.

Fixed

  • GraphQL mutation registration for QuantityMutations, ReservationMutations, and WaitlistMutations (minor correction).
  • Day-service “Next” button enable/disable logic now accounts for day-range capacity picker state.

Version 1.0.2

April 2, 2026

Fixed

  • Bundled Alpine.js with the plugin so the built-in booking wizard works out of the box without requiring the site theme to include Alpine separately (#3)
  • Alpine.js is loaded at POS_END to ensure proper initialization order with wizard components
  • Added detection to skip loading Alpine.js if the site already includes it