Appearance
v0.1.6
Release date: May 3, 2026
New Features
EU-Compliant Price Reductions
- Products have a new "Show previous price" toggle on the product form to opt in to displaying a price reduction
- The strikethrough on the storefront now uses the lowest price from the past 30 days, in line with the EU Omnibus Directive — not the manually entered prior price
- The product form warns when no sale badge will display (because the current price is not lower than the 30-day low), so admins know their reduction will not show
- Price changes are recorded automatically and a weekly cleanup job purges history older than 90 days
Unit Pricing
- Products can now declare a unit quantity and unit (kg, g, L, ml, m, cm, m²), and the storefront shows a "per kg" / "per L" / etc. line under the price on cards, the product page, collection, and search
- Count-based products (sold "per item") are exempt from unit pricing as required by the directive
- Unit pricing is exported to the Google Merchant feed as
unit_pricing_measureandunit_pricing_base_measure
Tax-Inclusive Storefront Prices
- All storefront prices (cards, product page, cart, checkout, order emails) now always include tax, as required for B2C sales in the EU
- The product page shows a notice when a tax rule has per-country overrides, signaling the final amount may be recalculated at checkout once the customer's billing country is known
- The admin product form has a new tax preview that shows what each country will be charged when a tax rule has per-country overrides
Billing Address in Checkout
- The checkout flow has a new billing address step: keep the shipping address (default), pick a saved address, or enter a new one
- Customers can optionally save a custom billing address to their address book for future orders
- Changing the billing country clears the selected payment method and any in-flight Stripe PaymentIntent so taxes are recomputed for the new country
- Order confirmation emails and the customer order history page show shipping and billing addresses separately when they differ
- The admin order view shows shipping and billing side by side
Improvements
Analytics Dashboard
- Tables on the Traffic, Products, and Abandoned Carts pages now use server-side pagination, so large stores stay responsive
- Funnel chart UI was refined for clearer drop-off visualization
- Sessions from signed-in admins are excluded from analytics so the numbers reflect real customer activity
- Page paths are normalized so the same logical page no longer fragments across multiple rows
Orders
- New order source filter on the order list separates checkout orders from manually created orders
- Manual order creation blocks placeholder emails when a real customer email is available, so notifications go to the right inbox
Onboarding
- The Languages step now pre-selects English, Spanish, Dutch, and Portuguese (previously only English); admins can still adjust the list
Checkout Stability
- Advisory locks and serializable snapshot updates protect concurrent checkout actions from race conditions
- Dates in the checkout date selector and order emails now respect the customer's locale
Shipping & Locale
- Shipping minimum-order thresholds are now compared against subtotal + tax minus any merchandise discount, matching what the customer sees in the order summary (shipping-only coupons are excluded from the discount)
- Server-rendered storefront pages and order emails resolve language via the customer's stored preference, then their
localecookie, then the first store-enabled language, falling back to English — so a stale cookie no longer renders a language the store has since disabled
Operations
- A new optional
ENABLE_AUTH_RATE_LIMITenv var lets operators disable Better Auth rate limiting in CI and test environments; production behavior is unchanged unless the variable is explicitly set
Bug Fixes
- Cart merge now validates stock for both branches and notifies customers when quantities are adjusted
- A warning toast now fires two minutes before the cart reservation expires
- Stripe webhook handlers (succeeded and failed) are now idempotent and use atomic guards to prevent double-processing
- Coupon apply and remove are serialized via the checkout lock; single-use-per-customer coupons no longer count cancelled or payment-failed orders
- Shipping-coupon revalidation is skipped when the customer changes country before picking a shipping method
- The visitor cohort is persisted on the order snapshot so server-side purchase events stay in the right cohort
- The user edit form's Cancel button now resets fields without leaving them marked dirty
Security Fixes
- The page-builder iframe channel is now locked to same-origin postMessage
- Address PATCH and DELETE operations are ownership-bound at the SQL level
- Per-IP rate limiting is now enforced on the Stripe webhook endpoint
- Analytics aggregate queries now use parameterized
make_interval()calls instead of string interpolation - Stripe
constructEventtimestamp tolerance is pinned to 300 seconds Order.stripePaymentIntentIdnow has a unique index and the webhook usesfindUniquefor safety