The "cart" is a stub
Origin ships cart-shaped files, but there is no theme-side cart. sections/cart-dropdown.fluid in full:
{% comment %} Mini-cart dropdown rendered in the site header. {% endcomment %}
<div style="position:relative;display:inline-block">
<a href="/checkout" style="display:flex;align-items:center;gap:4px">View Cart</a>
</div>
and templates/cart.fluid says it outright: "Cart functionality is handled by the checkout system." The real cart state lives inside the Angular checkout (the shell's spaceUId preload adds the space as an order line item; multi-space carts are a checkout concern). Note the stub even links to /checkout (singular) — a dead path in both apps — which tells you how vestigial it is. For a new theme: skip the cart chrome entirely and send visitors straight to /checkouts/cn/booking?spaceUId=.... Don't build a client-side cart in the theme.
Embeds: where booking works, and where it doesn't
Every theme route also exists under /embed/... (resolved in ThemeV2RouteResolver, rendered through layouts/embed.fluid — chrome-less, designed for an iframe on the tenant's external marketing site). Two embed behaviours matter for checkout:
layouts/embed.fluidsets<base target="_top">. Any normal link inside the iframe navigates the parent window, and because the document's URL is on the storefront host, relative hrefs still resolve against the storefront domain. So an embedded space detail page (/embed/spaces/{uid}) reusestemplates/space-detail.fluidunchanged — its#bookOnlineBtnworks, and clicking it breaks the whole browser tab out of the host site intohttps://<storefront-host>/checkouts/cn/booking?.... That is the intended behaviour: checkout must never run inside a third-party iframe.- The embedded spaces listing (
/embed/spaces→templates/embed-spaces.fluid) deliberately has no booking path from its inline detail view. Its "View" button doesn't navigate;showSpaceDetailInline()intheme.jsclones the clicked card into a panel with a Back button so the visitor stays inside the iframe, and the only CTA it reconstructs is the quote button:
javascript /* Reuse the card's own quote button data so the detail view converts straight into a lead without leaving the iframe. */ const quoteBtn = card.querySelector('.js-quote-btn');
If your theme wants embed bookings rather than embed leads, don't try to run checkout in the iframe — render a real link out instead: either to the full space detail page (/t/spaces/{{item.UId}}) or directly to /checkouts/cn/booking?spaceUId={{item.UId}}. With <base target="_top"> in place a plain <a href> already escapes the iframe; add an explicit target="_top" on the anchor if your layout doesn't set the base tag.
The non-listing embed pages already behave this way — snippets/space-card.fluid only renders the inline-detail button when the embed listing opts in via EmbedInlineDetail; everywhere else the card's CTA is the normal /t/spaces/{{item.UId}} link, "so the button is never a dead click".
- The parent page sizes the iframe via
postMessage.embed.fluidpublishes{ type: 'alyta:embed:resize', height }from aResizeObserveron the body — the type string matches the legacy Angular/embed/unitsbroadcaster so existing tenant sites keep working. Not checkout-specific, but if you replace the embed layout, keep this publisher or the tenant's iframe stops auto-sizing.
Checklist for a custom theme
- [ ] Book Online link =
/checkouts/cn/booking?spaceUId={Space.UId}, appending&months=Nonly when N > 1 and&promotionUId={uid}only when a promotion is selected. Same-host relative URL, GUIDUIdnot intId. - [ ] CTA gated on
Settings.ShowBookOnlineandSpace.AllowOnlineBookings; quote CTA gated onSettings.ShowGetQuote; all pricing hidden underSettings.HidePricing. - [ ] Live pricing via
GET /api/public/spaces/{id}/price?quantity=Nand.../promotions?quantity=N; promotions sorted bypriorityascending (nulls first); the selected promotion object replaces the base price object; only rely on the fields listed in §3. - [ ] Quote =
POST /api/public/spaces/{id}/get-quotewith{ name, email, phone, contactPreference, recaptchaToken }, reCAPTCHA Enterprise actiongetquotewhenSettings.RecaptchaSiteKeyis set, integer space id. - [ ] No theme-side cart; no theme page under a
checkoutpath (the router excludes it anyway). - [ ] Embeds: quote-only inside the iframe, or link out with
target="_top"for bookings; keep thealyta:embed:resizeheight publisher.