Consolidates the current situation for the team taking over: what shipped 2026-09-04, the blocking migration + app-proxy items, the known bug list (B1-B9), a prioritised next-step plan, and the infra/gotcha notes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
13 KiB
Team Handover — Delivery Date & Time Shopify App
Date: 2026-09-07
Branch: main @ f8dd5b2
Prepared for: engineering team taking over active development + the test rollout
Read this alongside:
PRODUCT_STRATEGY.md— the why / feature rationaleIMPLEMENTATION_PLAN.md— the phased build plan + architectureIMPLEMENTATION_REVIEW_2026-09-04.md— gap audit vs. the DS competitor study, and what was closedCLAUDE.md— the non-negotiables (server-side enforcement, DST-safe time, one scheduling service, multi-tenant, minimal scopes)
1. Where the project is
Greenfield build, Phases 0–8 substantially done (Phase 8 = billing + BfS hardening; a11y / perf / empty-state polish deferred to live testing). The app is deployed to a real production server (metatron-delivery.thedomainnest.com, PM2 + colocated Postgres/Redis) and is being tested against a password-protected dev store (Spark theme).
What works today (server-side, unit-tested): the scheduling engine (templates / overrides / blackouts / cutoffs / lead times / capacity, DST-safe), slot-holds with an atomic Redis race test, multi-location + postal/radius/driving zones + nearest-location auto-assign, product rules, rate resolution, the dispatch dashboard, billing tiers + feature gating, GDPR handlers (wired, webhooks disabled — see §4), and the Cart/Checkout Validation Function.
156 unit tests pass (npm test). Function WASM tests and integration (Redis/DB) tests run in CI only.
2. What shipped in the last batch (2026-09-04)
| Commit | What |
|---|---|
a2c78d7 |
DS study gap closure — see IMPLEMENTATION_REVIEW_2026-09-04.md §"Implementation follow-up". Inventory-based location exclusion wired in; live slot re-validation at checkout via a shop-metafield capacity snapshot; scopable enforcement (Shop.enforcementMode); per-day order cap (Location.dailyOrderCap); product-rule slot blocking (ProductRule.blockedStartMins); product-page preview block; French locale; new payment-customization Function; checkout-datetime Checkout UI Extension source restored. |
3749b4d |
Widget decodes HTML entities in theme label attributes (Couldn't… → Couldn't…). |
d98b29e |
UNLOCK_ALL_FEATURES env flag — getShopTier() returns "pro" so all plan-gated screens open for testing. |
f8dd5b2 |
Fixed inert Polaris <Select> dropdowns (Weekly slots, Blackout dates, Rates) — they need value+onChange in this Polaris/App-Bridge version. |
New DB migration: prisma/migrations/20260904120000_review_gaps/ — adds Location.dailyOrderCap, ProductRule.blockedStartMins, Shop.enforcementMode, Shop.enforcementTag. Not yet applied on the test server — this is the #1 action item below.
3. Immediate action items (do these first)
3.1 Apply the migration on the test server — BLOCKING
The regenerated Prisma client now SELECTs the new columns. Every loader that does db.location.findMany() / findFirst() without an explicit select (Weekly slots, Blackout dates, Checkout enforcement pages) and the storefront /apps/scheduling/availability endpoint will throw column ... does not exist until the migration runs. This is almost certainly why the cart widget currently shows "Couldn't load available dates" and why the admin slot/blackout pages misbehave.
cd <app dir on server>
git pull
npx prisma migrate deploy # applies 20260904120000_review_gaps
npx prisma generate
pm2 restart <process> # or: pm2 restart ecosystem.config.cjs
pm2 logs # confirm no "column X does not exist"
Fold npx prisma migrate deploy into the deploy pipeline (there isn't one yet — see §6).
3.2 Redeploy the admin app + widget bundle
f8dd5b2 and 3749b4d change Remix routes and the widget asset (extensions/datetime-widget/assets/datetime-widget.js, already rebuilt & committed). Restart the Remix server for the route fixes; run shopify app deploy to push the theme app extension (widget) to the store.
3.3 Verify the storefront widget path end-to-end
On the cart page console:
fetch('/apps/scheduling/availability?method=SHIPPING&days=14').then(r=>r.text()).then(console.log)
- nginx 403 → the app-proxy backend is blocking
/apps/scheduling/*. Checkpm2 statusand the nginx site config forwards it. (A raw403 Forbidden / nginxwas seen during testing — unresolved, needs a look at the server's nginx rules.) - Shopify 404 → App Proxy not registered. Partner Dashboard → App setup → App proxy: prefix
apps, subpathscheduling, URLhttps://metatron-delivery.thedomainnest.com/apps/scheduling, thenshopify app deploy. {"error":"No active location configured"}→ add a Location + weekly slot templates in the admin.{... "dates":{}}→ reachable but no matching slots (weekday/method/cutoff/lead filtering everything out).
3.4 Test-mode toggles (remove before any real launch)
.envon the server:UNLOCK_ALL_FEATURES=true→ all plan gates open. Restart PM2.automatically_update_urls_on_dev = falseinshopify.app.tomlis deliberate — do not runshopify app devagainst this app config or it overwrites the live URL with a tunnel. Test extensions against the real domain.
4. Known bugs / open issues
| # | Issue | Where | Notes / next step |
|---|---|---|---|
| B1 | Storefront widget: "Couldn't load available dates" | cart page | Most likely §3.1 (migration) + §3.3 (app-proxy 403). Re-test after both. |
| B2 | App-proxy backend returned 403 Forbidden / nginx during testing | metatron-delivery.thedomainnest.com/apps/scheduling/* |
Inspect the server's nginx config — is /apps/scheduling/ proxied to the Remix port, any deny / auth / IP allowlist? |
| B3 | Polaris <Select> inert without value+onChange |
admin forms | Fixed for slots/blackouts/rates (f8dd5b2). Audit rule for the team: every Polaris Select/TextField/Checkbox/ChoiceList in a form must be controlled — this Polaris + App Bridge version freezes uncontrolled inputs (cf. commit cc20f20). |
| B4 | checkout-datetime extension ships tsconfig checkJs:false |
extensions/checkout-datetime/ |
Source was restored from a dist/-only state and not yet reconciled against live @shopify/ui-extensions checkout types. Before shipping: npm install in the folder, flip checkJs:true, fix the <s-*> component props, verify on a Plus dev store. Its README has the checklist. |
| B5 | payment-customization Function has no committed schema.graphql / generated/ |
extensions/payment-customization/ |
By design (gitignored, codegen'd). Run npm --prefix extensions/payment-customization run typegen then shopify app deploy; merchant activates it under Settings → Payments. Not in CI's test:functions yet — its pure logic is covered by tests/unit/payment-customization.test.ts. |
| B6 | GDPR compliance webhooks commented out | shopify.app.toml |
Handlers exist and are wired (webhooks.customers.data_request, .customers.redact, .shop.redact). Blocked on Shopify granting Protected Customer Data Access (Partner Dashboard questionnaire). Re-enable the three [[webhooks.subscriptions]] blocks before any public/BfS submission. |
| B7 | read_customers scope intentionally dropped |
shopify.app.toml |
Needed only for the "recognize returning customers" parity item — add back with B6's approval when that feature is built. |
| B8 | Checkout snapshot 30-day horizon can go stale | jobs/worker.ts (TODO), checkout-snapshot.server.ts |
Refreshed inline on order webhooks + slot/blackout/location/enforcement edits, which covers real usage. A periodic worker sweep to keep the rolling horizon advancing on a zero-activity store needs offline-session storage first (so the worker process can get an Admin client per shop). |
| B9 | CI typecheck:checkout step |
.github/workflows/ci.yml |
Was failing before this batch (the extension had no package.json). Now restored; will pass once deps install. Watch the first CI run after this handover. |
5. Next-step implementation (prioritised)
From IMPLEMENTATION_REVIEW_2026-09-04.md plus the deferred list. Rough order:
P1 — finish what's in flight
- Reconcile
checkout-datetimewith live types + test on Plus (B4). The server routes it calls (checkout.scheduling.*) are already done and tested. - Deploy + smoke
payment-customizationon a dev store with a manual COD gateway (B5). - Rates: decide display-only vs. real pricing. Today
delivery-customizationonly renames the delivery option to append a price label; it does not set the charged amount. If real location/method-based rates are required (DS's core value prop), that's adelivery-customizationpricing operation or Shopify carrier-rate integration — scope it as its own task.
P2 — parity gaps still open
- Multi-pin pickup-location map — the widget currently maps a single resolved location; DS shows a chooser across all pickup points.
- Returning-customer recognition — needs B6/B7 (Protected Customer Data +
read_customers). - Cart Transform / deposits — v2 per the plan (
write_cart_transformsscope already requested).
P3 — BfS / launch readiness
- Re-enable GDPR webhooks once access is granted (B6).
- Accessibility + performance pass, empty/loading/error states, onboarding polish — the deferred Phase 8 items. Best done against the live store now that it exists.
- Deploy pipeline (§6).
- Remove test toggles —
UNLOCK_ALL_FEATURES, confirmautomatically_update_urls_on_dev, review scopes.
P4 — v1.x fast-follow (from IMPLEMENTATION_PLAN.md Phase 9)
Waitlists, self-service reschedule portal, email/SMS reminders (BullMQ + a Notification model), printable run sheets, holiday auto-import.
6. Environment, infra, and gotchas
Deployment
- Production server:
metatron-delivery.thedomainnest.com, Remix under PM2 (ecosystem.config.cjs), Postgres + Redis colocated.PORT=11002(see server.env). - No deploy pipeline yet. Current process is manual:
git pull→npm ci→npm run setup(prisma generate && prisma migrate deploy) →pm2 restart.npm run buildfor the Remix admin build. Automate this — the missingmigrate deploystep is exactly what caused the current breakage. - Extensions (theme widget, Functions, POS, checkout) deploy separately via
shopify app deploy— needs interactive Shopify CLI login (see below).
Shopify CLI
shopify app dev/deploy/config linkrequire interactive login — they won't run headless.shopify app generateandfunction typegenwork fine non-interactively.- Do not
shopify app devagainst this app config:automatically_update_urls_on_dev = falseprotects the live URL, but a dev session still can't serve the embedded admin through its tunnel — test extensions only, against the real domain. shopify.app.tomlhas a functionally-inert[events]placeholder subscription — required by the CLI schema for this org's Next-Gen Events preview enrolment. Leave it; it just logs and 200s.
Local dev
- Node 22.23.2 via nvm-windows. Global CLI tools (
shopify, etc.) must be reinstalled per Node version after anynvm use. - Postgres, not SQLite — the schema uses Prisma enums + scalar-list arrays.
docker compose up -dstarts local Postgres:5433 / Redis:6380 (see.env.example). The server's own DB is on 5432. npm test(Vitest unit) ·npm run test:integration(needs Redis+DB) ·npm run test:functions(needs Shopify CLI for the WASM build) ·npm run test:e2e(Playwright) ·npm run build:widget(esbuild, no deps).
Architecture invariants (from CLAUDE.md — enforce in review)
- Enforcement is server-side. The widget/POS/checkout extensions only collect; the
validation-slotFunction enforces. Anything that matters must go through it (now including the capacity snapshot). - One
resolveAvailabilityRequest+ one capacity pool feeds web / checkout / POS. Never fork the logic per surface. - Pure scheduling core in
app/services/*.server.ts— inject data, no DB/Shopify I/O inside the math, so it stays unit-testable.app/lib/time.tsis safety-critical (DST); don't weaken its tests. - Every query scoped by
shopDomain. Multi-tenant. - Polaris only in the admin UI (BfS). Controlled inputs only (B3).
Key files
| Area | File |
|---|---|
| Availability resolver (all surfaces) | app/services/availability-request.server.ts |
| Pure scheduling engine | app/services/scheduling.server.ts |
| Slot-holds (Redis, atomic) | app/services/holds.server.ts, hold-request.server.ts |
| Checkout capacity snapshot | app/services/checkout-snapshot.server.ts |
| Zones / geocoding / inventory exclusion | app/services/zones.server.ts |
| Product rules | app/services/product-rules.server.ts |
| Billing / tier gating | app/services/billing.server.ts, app/lib/billing-plans.ts |
| DST-safe date math | app/lib/time.ts |
| Storefront widget source | widget-src/datetime-widget/datetime-widget.ts → extensions/datetime-widget/assets/datetime-widget.js |
| Validation Function | extensions/validation-slot/src/ |
| Delivery / payment customization Functions | extensions/delivery-customization/src/, extensions/payment-customization/src/ |
| Checkout UI extension | extensions/checkout-datetime/src/ |
| Order webhooks (booking + snapshot refresh) | app/routes/webhooks.orders.*.tsx |