metatrondelivery/HANDOVER.md
metatroncubeswdev 8650b1cb0c
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
docs: add team handover — state, deploy steps, open bugs, next-step plan
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>
2026-09-07 01:21:38 -04:00

158 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 rationale
- `IMPLEMENTATION_PLAN.md` — the phased build plan + architecture
- `IMPLEMENTATION_REVIEW_2026-09-04.md` — gap audit vs. the DS competitor study, and what was closed
- `CLAUDE.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 08 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&#39;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 `SELECT`s 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.
```bash
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:
```js
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/*`. Check `pm2 status` and the nginx site config forwards it. (A raw `403 Forbidden / nginx` was 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`, subpath `scheduling`, URL `https://metatron-delivery.thedomainnest.com/apps/scheduling`, then `shopify 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)
- `.env` on the server: `UNLOCK_ALL_FEATURES=true` → all plan gates open. Restart PM2.
- `automatically_update_urls_on_dev = false` in `shopify.app.toml` is deliberate — do **not** run `shopify app dev` against 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
1. **Reconcile `checkout-datetime` with live types + test on Plus** (B4). The server routes it calls (`checkout.scheduling.*`) are already done and tested.
2. **Deploy + smoke `payment-customization`** on a dev store with a manual COD gateway (B5).
3. **Rates: decide display-only vs. real pricing.** Today `delivery-customization` only *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 a `delivery-customization` pricing operation or Shopify carrier-rate integration — scope it as its own task.
### P2 — parity gaps still open
4. **Multi-pin pickup-location map** — the widget currently maps a single resolved location; DS shows a chooser across all pickup points.
5. **Returning-customer recognition** — needs B6/B7 (Protected Customer Data + `read_customers`).
6. **Cart Transform / deposits** — v2 per the plan (`write_cart_transforms` scope already requested).
### P3 — BfS / launch readiness
7. **Re-enable GDPR webhooks** once access is granted (B6).
8. **Accessibility + performance pass, empty/loading/error states, onboarding polish** — the deferred Phase 8 items. Best done against the live store now that it exists.
9. **Deploy pipeline** (§6).
10. **Remove test toggles**`UNLOCK_ALL_FEATURES`, confirm `automatically_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 build` for the Remix admin build. **Automate this** — the missing `migrate deploy` step 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 link` require **interactive login** — they won't run headless. `shopify app generate` and `function typegen` work fine non-interactively.
- Do **not** `shopify app dev` against this app config: `automatically_update_urls_on_dev = false` protects 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.toml` has 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 any `nvm use`.
- **Postgres, not SQLite** — the schema uses Prisma enums + scalar-list arrays. `docker compose up -d` starts 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-slot` Function 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.ts` is 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` |