- Fix vinyl add-on checkout: product line item was dropped when vinyl selected; entryUnitPrice also excluded base product price
- Store vinyl per-letter price on cart entry so CartDrawer charges the config price, not hardcoded 65¢
- Fix two bare modifiers.find() calls (use optional chaining) to prevent checkout crash on bad data
- Validate deliveryCents (must be non-negative integer) and customer name fields (no control chars) in checkout API
- Validate rateOverride values are non-negative numbers in delivery-quote API
- Add RFC 5545 iCalendar escaping to SUMMARY/LOCATION/DESCRIPTION fields to prevent calendar injection
- Add public /api/hours route; pickup and delivery calendars now fetch admin-saved hours and pre-grey closed days
- Reset delivery quote and slot when high-rate item is removed from cart
- Change delivery window copy from 2 hours to 1 hour (DeliveryDatePicker + terms page)
- Fix SVG paths: /color/images/ → /color-picker/images/ (balloon mask, shine, color backgrounds); was causing Safari ? placeholders
- Enlarge padlock icon in PaymentForm from 11px to 14px for better alignment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Items can now be marked as "requires delivery" in admin — these items
cannot be picked up and must be delivered (and struck).
- Admin item editor: "Requires delivery" checkbox + custom base/per-mile
rate fields that appear when the toggle is on
- ProductCard: "Delivery & setup required" note on the card
- CartDrawer: pickup toggle is hidden and replaced with an explanation
when any cart item requires delivery; the quote call passes the
item's custom rate override (highest base + highest per-mile wins
when multiple requires-delivery items are in the cart)
- delivery-quote API: accepts optional rateOverride to apply per-item
pricing on top of the inferred tier
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>