metatrondelivery/IMPLEMENTATION_REVIEW_2026-09-04.md
metatroncubeswdev a2c78d703f
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
feat: close DS study coverage gaps (inventory exclusion, live checkout re-validation, per-day cap, payment fn, checkout ext)
Audited the implementation against DS_Delivery_Date_Time_App_Study.docx and
closed the actionable gaps (see IMPLEMENTATION_REVIEW_2026-09-04.md).

Core (code + unit tests, 156 green):
- Wire excludeLocationsWithoutStock into resolveAvailabilityRequest; widget
  now sends variantIds so inventory-based location exclusion actually runs.
- Live slot re-validation at checkout: new checkout-snapshot.server.ts writes
  a shop-metafield capacity snapshot; validation-slot's evaluateCheckout
  rejects a complete selection that has since filled / blacked out / closed /
  hit the daily cap / left the schedule. Refreshed on order webhooks and
  slot/blackout/location/enforcement edits.
- Scopable checkout enforcement: Shop.enforcementMode (all|tagged|off) +
  enforcementTag, new app.settings.tsx admin page, honoured via the snapshot.
- Per-day order cap: Location.dailyOrderCap threaded through getAvailability
  (dailyCap + consumedPerDate); admin field on the location screen.
- Product-rule slot blocking: ProductRule.blockedStartMins, unioned in
  resolveProductRuleConstraints, enforced in the engine and resolveHoldRequest;
  admin field on the product rules screen.
- Product-page placement: product-availability.liquid block + widget
  data-mode="preview" (read-only earliest-date line).
- Second locale: datetime-widget fr.json / fr.schema.json.
- Migration 20260904120000_review_gaps (apply with prisma migrate deploy).

New Functions (source + unit tests; need `shopify app deploy` to ship):
- extensions/payment-customization: cart.payment-methods.transform.run — hides
  cash-on-delivery / pay-in-store gateways on SHIPPING orders.
- extensions/checkout-datetime/src: restored from a gitignored dist-only state
  — Plus native picker + Thank you / Order status confirmation blocks, all
  calling the existing checkout.scheduling.* routes (one capacity pool).
  tsconfig ships checkJs:false pending reconciliation with live checkout types.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 01:31:02 -04:00

11 KiB

Implementation Review — 2026-09-04

Scope: Coverage audit of the current implementation against DS_Delivery_Date_Time_App_Study.docx (the "DS Pickup Delivery Date & Time" teardown by C2C).

Reviewed: app/ (routes, services, lib), extensions/, prisma/schema.prisma, widget-src/, shopify.app.toml, tests/.

Verdict: The scheduling core, concurrency safety, multi-surface consistency, zones/auto-assignment, and all-plan checkout enforcement + confirmation — the hard parts and the competitive moat — are solid and tested. The meaningful gaps are inventory-based location exclusion (dead code), payment customization (absent), rate application vs. display, product-page placement, and live slot re-validation at checkout. Several gaps are consciously documented deferrals; the inventory one looks like an actual wiring bug.


Fully covered

Study requirement Where
3 independent fulfillment methods (Shipping / Local Delivery / Pickup) with own rules, calendars Method enum; per-method SlotTemplate / SlotOverride / BlackoutDate
Date→time slot picker; unavailable dates/slots hidden, not rejected app/services/scheduling.server.ts:89 filters past-cutoff / under-lead / blacked-out / full
Cut-off times + preparation/lead time (incl. per-product) cutoffMin, leadTimeMin on template; ProductRule.leadTimeMin folded in at app/services/availability-request.server.ts:210
Blackout dates; per-weekday / per-method / per-location enable-disable BlackoutDate, SlotTemplate per weekday, SlotOverride.closed
Per-slot capacity limit capacity + remainingCapacity() in app/services/capacity.server.ts
Multi-location; radius and driving-distance zones; postal zones; nearest-location auto-assign; delivery-density threshold app/services/zones.server.ts
Product / collection / vendor / type / tag rules → allowed methods, lead time, allowed locations app/services/product-rules.server.ts
Rate resolution by zone / distance per method app/services/rates.server.ts
Cart + cart-drawer placement; cross-theme auto-inject before checkout button widget-src/datetime-widget/datetime-widget.ts:570
Thank-you / order-status confirmation on all plans extensions/datetime-widget/blocks/order-confirmation.liquid
POS scheduling against the same capacity pool app/routes/pos.scheduling.* reuse resolveAvailabilityRequest
Last-slot race prevention atomic Lua hold in app/services/holds.server.ts:22 + tests/integration/holds.concurrency.test.ts
Server-side checkout enforcement on non-Plus extensions/validation-slot Function
Single availability service across surfaces; multi-tenant shopDomain scoping; DST-safe Luxon math
Order write-back (metafield + note attributes) app/routes/webhooks.orders.create.tsx
Ops dashboard: bookings by day/slot/location, utilization, revenue by method, CSV export app/services/dashboard.server.ts
DS review complaint (pickup vs "estimated delivery date" mismatch) explicitly fixed via method-specific labels + delivery-customization rename

Partial / deviations from the study

  1. Inventory-based location exclusion is coded but never called. excludeLocationsWithoutStock (app/services/zones.server.ts:241) has zero callersresolveAvailabilityRequest never invokes it. Study §3.4 "product-inventory-based location choice" is effectively absent at runtime. Looks like a wiring bug, not a deliberate deferral.

  2. Rates are displayed, not applied. delivery-customization only renames the delivery option to append a price label (extensions/delivery-customization/src/evaluate.js:23). Nothing adjusts the actual charged shipping price or feeds a Shopify rate. Study §3.6 / the DS core value prop ("location-based shipping/delivery/pickup rates") is only half there.

  3. Checkout re-validation checks presence only. validation-slot (extensions/validation-slot/src/evaluate.js) verifies the dd_* attributes exist; it does not re-check that the slot is still open (capacity / blackout / cutoff). Study §5.2 explicitly names "or has since become invalid." Mitigation is the 10-minute hold TTL, which the code comments acknowledge.

  4. Validation Function can't be scoped by product. Every order on an activated shop is treated as requiring a slot — no ProductRule-scoped enforcement (Functions can't read the app DB). A store mixing schedulable and non-schedulable items can't use it selectively.

  5. "Date range" selection reinterpreted. The schema/engine implement a SHIPPING arrival-range display (transitMinDays / transitMaxDays), not a shopper-selectable multi-day range. Study §3.2 "support for picking a range rather than a single date."

  6. Product-rule "block a specific slot based on cart contents" (§3.5, "fragile item that can't go on the earliest slot") is only expressible as a lead-time push. The planned blockSlotRule Json? field was dropped from schema.prisma.

  7. Pickup map is single-location. The widget maps the one resolved location (widget-src/datetime-widget/datetime-widget.ts:427); there's no multi-pin pickup-location chooser. Study §3.4 "pickup locations shown visually on Google Maps."

  8. Multi-language is structure-only. Locale keys exist but only en.default.json ships — no second translation.

  9. Per-day order cap not modeled — only per-slot capacity. Study §3.3 "in a given slot or day."


Not implemented

  • Payment customization (§5.2 — "hide cash-on-delivery for a shipped order"). write_payment_customizations scope is requested but no such Function exists.
  • Product-page placement (§3.7). No product-page block — only cart / drawer / thank-you / order-status / POS.
  • Checkout UI Extension source (extensions/checkout-datetime/) — only a gitignored dist/ bundle; no src/ or shopify.extension.toml tracked. The Plus native picker is not a maintained deliverable. (The all-plan Liquid confirmation block does cover thank-you / order-status.)
  • Returning-customer recognition (§3.8) — read_customers deliberately dropped; documented as deferred.
  • Cart Transform (deposits / fees) — scope requested, marked v2.
  • Named capacity resources (oven / driver / picker pools) — capacity is a single integer; CapacityResource from the plan was never added.
  • GDPR compliance webhooks are commented out in shopify.app.toml pending Shopify's Protected Customer Data approval (handlers exist). Blocks a Built-for-Shopify submission.

Implementation follow-up — 2026-09-04 (same day)

Actioned in this pass ("Core + new Functions" scope; Cart Transform / deposits, returning-customer recognition, and GDPR re-enable stay deferred).

Done — code + passing unit tests (npm test, 156 green)

  1. Inventory-based location exclusion is now wired. resolveAvailabilityRequest calls excludeLocationsWithoutStock when the app-proxy surface supplies an Admin client + cart variant GIDs; the widget now sends variantIds. Out-of-stock locations are dropped from both the single-location and delivery-zone paths, with a dedicated "no location stocks every item" message.
  2. Live slot re-validation at checkout. New app/services/checkout-snapshot.server.ts writes a shop-metafield capacity snapshot; the validation-slot Function (evaluateCheckout) now rejects a complete selection when the snapshot shows the slot is full, blacked out, closed, past the daily cap, or gone from the schedule. Snapshot is refreshed on orders/create|updated|cancelled and on slot / blackout / location / enforcement edits.
  3. Scopable checkout enforcement. Shop.enforcementMode (all / tagged / off) + enforcementTag, new Checkout enforcement admin page (app.settings.tsx), honoured by the Function via the snapshot's enforcement block and schedulableProductIds.
  4. Per-day order cap. Location.dailyOrderCap, threaded through getAvailability (dailyCap + consumedPerDate) — the whole date drops once its bookings reach the cap, and per-slot remainingCapacity is clamped to the day's remaining budget. Admin field on the location edit screen.
  5. Product-rule slot blocking (§3.5). ProductRule.blockedStartMins, unioned in resolveProductRuleConstraints, applied in getAvailability and re-enforced server-side in resolveHoldRequest. Admin field on the product rules screen.
  6. Product-page placement. New blocks/product-availability.liquid + widget data-mode="preview" — a read-only "earliest available date per method" line that collects nothing.
  7. Second locale. extensions/datetime-widget/locales/fr.json + fr.schema.json (French), proving the i18n path end-to-end.

New migration: prisma/migrations/20260904120000_review_gaps (hand-written; apply with prisma migrate deploy — the dev DB was unreachable here).

Done — source + unit tests, needs shopify app deploy / CLI to ship

  1. payment-customization Function — new extensions/payment-customization/ (cart.payment-methods.transform.run): hides cash-on-delivery / pay-in-store gateways on SHIPPING orders. Pure logic unit-tested (tests/unit/payment-customization.test.ts). Run npm --prefix extensions/payment-customization run typegen then shopify app deploy; merchant activates it under Settings → Payments.
  2. checkout-datetime Checkout UI Extensionsrc/ restored (was a gitignored dist/ only): Checkout.jsx (Plus native picker → checkout.scheduling.* routes, same capacity pool), ThankYou.jsx, OrderStatus.jsx. tsconfig ships checkJs:false until the <s-*> props are reconciled against the live @shopify/ui-extensions checkout types (see its README) — the server side it calls is already tested.

Not done (unchanged from above)

  • Cart Transform / deposits — v2.
  • Returning-customer recognition — needs read_customers + Protected Customer Data approval.
  • GDPR compliance webhooks — still blocked on Shopify approval.
  • Multi-pin pickup-location map picker — still single-location.
  • Rates remain display-only (delivery-customization renames the option; it doesn't set the charged price). Left as a product decision.
  • Worker periodic snapshot sweep — event-driven refresh covers the real cases; the 30-day horizon sweep needs offline-session storage first (noted in jobs/worker.ts).

Suggested priority order

  1. Wire excludeLocationsWithoutStock into resolveAvailabilityRequest (or delete it if intentionally cut). — bug
  2. Decide rate strategy: real delivery-customization pricing / Shopify rate integration vs. accept display-only and document it.
  3. Live slot re-validation at checkout (metafield capacity snapshot the Validation Function can read) — closes the §5.2 "since become invalid" gap.
  4. Product-page placement block.
  5. Payment customization Function (COD-on-shipping etc.).
  6. Product-scoped enforcement toggle for the Validation Function.
  7. Second locale + per-day cap + multi-pin pickup map as smaller follow-ups.
  8. Re-enable GDPR webhooks once Protected Customer Data access is granted (launch blocker for BfS).