16 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aa14f34e36 |
chore: temporarily exclude checkout-datetime from shopify app deploy
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
Its @shopify/ui-extensions-react dependency has no release matching the
declared api_version = "2025-10" (the package jumps straight from 2025.7.4
to 2026.0.0, skipping 2025.10.x entirely) — a real migration, not a quick
version bump, and already tracked as HANDOVER.md's open item B4 ("not yet
reconciled against live checkout types").
This extension has never successfully deployed even once, so nothing
currently live regresses by excluding it — but its build failure was
aborting deploy for every OTHER extension too (esbuild fails the whole
`shopify app deploy` when any one extension can't bundle), which meant
shopify.app.toml's App Proxy URL fix never actually reached Shopify's
servers despite being correct in the repo. Renaming its config file
(shopify.extension.toml -> .disabled) excludes it from discovery without
touching its source; re-enable once B4's version migration is done by
renaming it back.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
b29962bf9c |
feat: finish multi-pin pickup end-to-end; fix checkout-datetime followups
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
Multi-pin pickup selection (study §3.4) — previously a standalone unused endpoint + TODO comments: - app/services/pickup-locations.server.ts: pure filterPickupLocationIds + I/O resolvePickupLocations (active + has a PICKUP slot template + ProductRule allowedLocationIds + inventory exclusion). - resolveAvailabilityRequest returns pickupLocations[] on method=PICKUP and defaults the active location to the first eligible pickup point. - apps.scheduling.locations.tsx refactored onto the shared resolver (was a second copy of the logic). - Storefront widget: pickup-location chooser (name + address, optional multi-pin Google map) when >1 eligible point and none block-configured; picking one re-requests availability for that location. New styles + widget.choose_pickup_location locale (en + fr). - Checkout extension Checkout.jsx: same chooser before the date list. - tests/unit/pickup-locations.test.ts (5 cases); suite 161 green. checkout-datetime follow-up fixes from the 808a3b7 review: - typescript devDep ^7.0.2 -> ^5.6.3 (there is no typescript@7 on npm). - Deleted dead shopify.d.ts (Preact-global shim, unused after the React rewrite) and dropped it from tsconfig include. Also stages the CLI-written `uid` lines in the checkout-datetime and payment-customization extension tomls. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
| 808a3b7001 |
changes - UI Functionality
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
|
|||
|
|
3749b4d134 |
fix: decode HTML entities in storefront widget label attributes
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
Liquid's `| escape` filter encodes apostrophes/ampersands before the label strings land in data-* attributes; depending on how the theme processes translations that could survive into `dataset` still entity-encoded and then render literally (e.g. "Couldn't load available dates") once assigned to textContent. readConfig now decodes entities once via a detached <textarea>, routed through a shared get(key, fallback) helper for every label + heading. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a2c78d703f |
feat: close DS study coverage gaps (inventory exclusion, live checkout re-validation, per-day cap, payment fn, checkout ext)
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
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> |
||
|
|
1262e0f5ab |
feat: cross-theme cart placement for the storefront widget
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
Many themes only expose "Add section" for the cart's checkout area, not "Add block" next to the actual Checkout button — Shopify gives apps no supported way to inject a block into an arbitrary spot in a theme's own section markup, so a manually-placed app block can only ever land as a disconnected standalone section on those themes. Add a JS-based fallback via the app embed (already loaded site-wide, independent of block placement): app-embed.liquid now carries the same config settings as the block plus an "auto-place on cart page" toggle (default on), emitted as an inert <template> with the widget's config as data-* attributes. datetime-widget.ts's maybeAutoPlaceOnCart() finds the theme's own Checkout button by CSS selector (a few common patterns, most specific first) and inserts a live widget immediately before it — skipped entirely if a block-placed widget already exists on the page, or no Checkout button can be found by any known selector. Also fix shopify.app.toml: automatically_update_urls_on_dev was left on after the app got a real, permanent production domain (metatron-delivery.thedomainnest.com) — leaving it on meant a future local `shopify app dev` session would silently overwrite the live app's application_url with a temporary Cloudflare tunnel, breaking the deployed app until someone noticed and redeployed with the real URL. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
| 03574a4914 |
feat: product rules, driving-distance zones, and shipping date ranges
Closes remaining DS-parity gaps from the feature audit: - ProductRule model (product/collection/vendor/type/tag scoping) with real server-side enforcement in hold-request.server.ts, plus shaped availability in availability-request.server.ts. Covers per-product prep time, cart-content-based slot blocking, and product-restricted locations in one mechanism. New /app/rules admin page (Growth+). - Driving-distance delivery zones via Google's Distance Matrix API, cached like existing geocoding results. - SHIPPING-only estimated arrival range (transitMinDays/transitMaxDays on SlotTemplate) — widget shows "Arrives Thu-Sat" instead of a meaningless ship-out time slot; carried through to the order metafield write-back. Storefront widget and POS extension now send cart contents (vendor/ type from cart.js, product ids for Admin-API-resolved collection/tag rules) to both availability and hold endpoints. checkout-datetime remains excluded from this deploy pending Shopify's Network Access approval (unrelated to this work) — re-add from ../checkout-datetime-disabled and redeploy once granted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
|
|
4a1d4e323f |
fix: add missing shopify.web.toml — root cause of dev preview never loading
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
shopify.web.toml never existed at the repo root. Without it, `shopify app dev` runs the theme/UI-extension/Function dev servers fine but never starts the actual Remix app, and has no local dev-server port to build a tunnel URL from for the embedded admin app — so Admin always showed the generic "Find this app in the pages where you work" fallback, regardless of which app record was linked, cache state, or CLI version. Root-caused via `shopify app dev --verbose`, which showed the CLI's own reverse proxy had a route for /extensions but none for /. Also updates shopify.app.toml's client_id to a freshly linked app (the previous one, and two accidental duplicates created while chasing this bug via `dev --reset`/`config link`, were deleted from the Dev Dashboard), resets application_url/redirect_urls to placeholders now that dev's auto-update actually works, restores checkout-datetime's network_access capability (briefly disabled to test an unrelated theory — confirmed not the real cause), and widens the dev-log gitignore pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
3f77c658d3 |
fix: populate empty checkout-datetime locale file
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
extensions/checkout-datetime/locales/en.default.json was an empty {}
(unlike pos-datetime and datetime-widget, which both have real content) —
shopify app dev's checkout preview refuses to start with "Locale data for
`en` is empty." Checkout.jsx/ThankYou.jsx don't call useTranslate yet (all
strings are hardcoded inline), so this just satisfies the non-empty
requirement and mirrors the extension's visible strings for when it is
wired up to i18n.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
5b2207a397 |
feat: Phase 7 — POS + Checkout UI extensions
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
Also fixes the [events] gate that was blocking ALL extension generation
(discovered while starting this phase).
- shopify.app.toml: this org appears enrolled in Shopify's "Next
Generation Events" developer preview, which the CLI now treats as a
REQUIRED top-level [events] section even though nothing in this app
actually uses it (real webhook handling is entirely classic [webhooks],
unaffected). Iteratively discovered the required shape from the CLI's
own field-by-field validation errors, then found the real docs (Events
is optional/developer-preview, api_version pinned to "unstable") to
confirm rather than keep guessing. Added a functionally-inert
[[events.subscription]] placeholder + a stub handler
(webhooks.events.placeholder.tsx) solely to satisfy the gate.
- app/services/availability-request.server.ts +
app/services/hold-request.server.ts: extracted the resolution logic that
used to live directly in apps.scheduling.availability.tsx/hold.tsx into
shared functions. This is what actually makes "same capacity pool feeds
every surface" (CLAUDE.md) true by construction rather than by
convention — the storefront, POS, and checkout routes now call the exact
same code, not three copies that could quietly drift apart.
- extensions/pos-datetime (generated via `shopify app generate extension
--template=pos_smart_grid` — pos_action's flavor requirement contradicted
the CLI's own global --flavor validator, so smart_grid was used instead):
a home-screen tile opening a modal where staff pick method -> date -> time
against the same availability/hold endpoints (pos.scheduling.*.tsx,
session-token authenticated), writing the same dd_* cart properties via
CartApi.addCartProperties — booking.server.ts needed zero changes to
handle POS-originated orders. Several API-shape guesses (toast isError
option, ChoiceList's `value`/`label` props, a nonexistent
action.dismissModal(), shopify.cart.cart.current) were wrong and caught
by typechecking directly against @shopify/ui-extensions' own bundled
.d.ts files (`npm run typecheck:pos`, now also in CI) — none of this was
verified against a live POS session, which isn't possible in this
environment.
- extensions/checkout-datetime (generated via `--template=checkout_ui`):
the Plus-only native picker in checkout itself
(purchase.checkout.block.render) plus a Thank You confirmation block
(purchase.thank-you.block.render). Went looking for an order-status
target too ("all plans show confirmed slot on thank-you/order-status" is
the Phase 7 accept criterion) and confirmed via the installed package's
own type definitions that purchase.order-status.block.render does not
exist in this API version — checkout UI extensions' thank-you/order-status
surfaces are Plus-only regardless. The actual "all plans" mechanism is
extensions/datetime-widget/blocks/order-confirmation.liquid — a new Theme
App Extension block reading order.note_attributes, which works on every
plan since it's plain Liquid, not checkout extensibility.
Both new UI extensions share one real unverified assumption, called out in
code comments: process.env.APP_URL is expected to be substituted at build
time by the Shopify CLI to the app's backend origin, since these run in a
different origin than the app and need an absolute URL, unlike the
storefront widget's relative /apps/scheduling/* path. Needs confirming
against a live dev session.
Verified: lint, typecheck (root + both new extensions'
`npm run typecheck:pos`/`typecheck:checkout`, all now in CI), 102 unit +
18 integration tests (unchanged — this phase didn't touch pure business
logic, only added thin auth wrappers around already-tested services), both
admin/widget builds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
6598691372 |
feat: Phase 5 — multi-location, zones, rates, auto-assignment
- Prisma: Zone (postal-code list or radius), Rate (zone- or distance-band
keyed), GeocodeCache (permanent address->lat/lng cache per
IMPLEMENTATION_PLAN.md §9), Location.shopifyLocationId (maps to
Shopify's own Location resource for inventory checks), Booking.zoneId
(needed for per-zone delivery-density counts, not just per-location).
- app/lib/geo.ts: pure haversine distance + postal-code matching, unit
tested against known city-to-city distances.
- app/services/zones.server.ts: geocoding (Google Maps Geocoding API,
cached — never re-geocodes the same address twice), zone eligibility,
nearest-location auto-assignment ranked by distance, delivery-density
threshold checks (a sparse zone doesn't unlock until minOrders bookings
have already routed through it), and inventory-based location exclusion
via Shopify's InventoryLevel API (locations without a mapped
shopifyLocationId are left in rather than false-negative excluded).
- app/services/rates.server.ts: pure rate resolution by zone or distance
band, cheapest-match-wins when bands overlap.
- apps.scheduling.availability.tsx: LOCAL_DELIVERY requests with a
postalCode/address now auto-assign to the nearest eligible,
density-qualified zone/location instead of the shop's default location;
response includes the matched rate. Also fixed a real gap left over from
Phase 4: this route never actually read Booking counts into
getAvailability's `consumed` map, so capacity always showed as fully
available regardless of existing bookings — now it does.
- extensions/datetime-widget: LOCAL_DELIVERY now asks for a postal code
before showing dates; PICKUP shows a Google Maps pin for the location
(both gated on an optional Maps API key — a block setting in the theme
editor, since it needs to be public/client-side, not an app secret);
confirmation display and cart attributes (dd_zone_id, dd_rate_label)
carry the resolved zone/rate through to checkout.
- extensions/delivery-customization: now appends the resolved rate to the
relabeled delivery option ("Local delivery — Aug 25 ($5.99)") when one's
configured — real Cart Transform-based fee *charging* stays deferred to
v2 per IMPLEMENTATION_PLAN.md §5.4, this is display-only.
- Admin: /app/zones and /app/rates (Polaris CRUD, mirroring Phase 1's
patterns), plus shopifyLocationId and auto-geocode-on-save added to the
location edit form.
Fixed one real bug caught only by `npm run build` (not tsc/vitest, which
both passed clean): app.rates._index.tsx's component called
formatPriceLabel from rates.server.ts, and Remix correctly refuses to
bundle anything imported from a .server.ts path for the client. Moved the
pure (no I/O, no Prisma) formatter to app/lib/currency.ts.
Verified: lint, typecheck, 86 unit tests (+21 new: geo, zones, rates,
delivery-customization's rate-label case with a real WASM fixture run),
16 integration tests against live Postgres (+8 new: geocode caching,
postal/radius zone matching, nearest-first ranking, density thresholds),
both builds, and a live script exercising the full
zone-match -> density-check -> rate-resolve -> availability pipeline
together against the Postgres container.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
e116a6c87a |
fix: Theme App Extension only allows assets/blocks/snippets/locales dirs
`shopify app dev` failed dev preview with "Only assets, blocks, snippets, locales directories are allowed" — the widget's TypeScript source lived in extensions/datetime-widget/src/, which isn't one of the four directories a Theme App Extension may contain. Moved it to widget-src/datetime-widget/ (a plain, non-extension folder outside extensions/) and updated build:widget's esbuild input path accordingly; the bundled output still lands in the same place (extensions/datetime-widget/assets/). Also fixed a theme-check warning surfaced during the same run: `script_tag` renders a parser-blocking <script> with no way to defer it — switched to a manual <script defer> tag for the widget's JS asset. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
381c52d01a |
fix: replace hand-scaffolded Functions with real CLI-generated ones
The user linked the app to a real Partner org and hit two live errors
running `shopify app dev`, which is exactly the verification the earlier
hand-scaffolded Functions couldn't get in this environment. Root-caused and
fixed both, then went further: regenerated both Functions from scratch via
`shopify app generate extension` (now possible — the user's session had
authenticated) instead of patching the guesses.
What broke and why:
- `shopify app config link` pulled a fresh app's (empty) remote config and
overwrote shopify.app.toml, dropping the webhook subscriptions and
app_proxy block — restored both, keeping the real client_id/name/scopes
the CLI set.
- `[extensions.build.watch]` as a nested table was invalid TOML for this
field — it's a plain `watch = [...]` array directly under
`[extensions.build]`.
- The real failure ("doesn't have a build command or it's empty") turned
out to be a red herring pointing at a stale filename
(shopify.function.extension.toml, not the current shopify.extension.toml)
— the actual problem was that `@shopify/shopify_function` was never
installed for these extensions (confirmed: no node_modules), because
hand-writing package.json doesn't run the install step
`shopify app generate extension` does automatically.
Rather than keep guessing at the toolchain, regenerated both Functions for
real:
- `shopify app generate extension --template=cart_checkout_validation` and
`--template=delivery_customization` (--flavor=vanilla-js), which produces
a working vite/vitest-based build+test setup, real
`@shopify/shopify-function-test-helpers` fixture testing (builds actual
WASM and runs it via function-runner), and a generated GraphQL type file
per extension.
- This surfaced several concrete corrections to what was hand-written
before: the real target names are `cart.validations.generate.run` and
`cart.delivery-options.transform.run` (not `purchase.validation.run` /
`purchase.delivery-customization.run`), current api_version is 2026-07
(not 2025-01), the validation output wraps errors in
`operations: [{ validationAdd: { errors } }]` with a plain `message`
field (not top-level `errors` with `localizedMessage`), and the rename
operation is `deliveryOptionRename: { deliveryOptionHandle, title }` (not
`rename: { deliveryOptionHandle, title }`).
- Rewrote each extension's `.graphql` input query to request our actual
dd_* cart attributes (plus delivery option handles for the rename case),
regenerated types via `npm run typegen` in each, and ported the pure
evaluate.js decision logic (same exported function names/behavior as
before, now proven correct against the live schema) into the adapter
file the generator expects.
- Replaced each extension's demo fixture with ones matching our real
logic; `npm test` inside each extension now compiles real WASM and runs
function-runner against them — this is strictly stronger verification
than the previous pure-JS-only unit tests (which are kept too, unchanged,
since the evaluate.js files kept the same interface).
Repo-wide wiring: extensions/*/generated and extensions/*/dist are not
committed (matches the CLI's own per-extension .gitignore) — added
`npm run typegen:functions` (runs automatically before `npm run typecheck`
via a pretypecheck hook) and `npm run test:functions`, both now also in CI.
Root `npm install` picked these two folders up as proper npm workspace
members (they already have their own package.json from generation).
Verified: lint, typecheck, all 52 unit tests, all 8 integration tests, both
extensions' real WASM/function-runner test suites (5 fixtures total), and
both `npm run build` / `npm run build:widget` all pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
7a340ad135 |
feat: Phase 4 — enforcement Functions + slot-holds
The core competitive moat (PRODUCT_STRATEGY.md §3.1): checkout can no
longer complete without a valid, still-available slot, on any Shopify
plan. User confirmed writing Functions in JS rather than Rust — no Rust
toolchain was available in this environment, and IMPLEMENTATION_PLAN.md §1
explicitly allows JS as a fallback ("Rust preferred, JS acceptable").
- app/services/holds.server.ts: Redis-backed soft slot-holds with TTL.
Hold creation is a Lua script (EVAL) — the "does this slot have room"
check and the reservation itself have to be one atomic Redis operation,
or two concurrent requests can both read "one spot left" and both
succeed. Outstanding holds per slot live in a sorted set scored by expiry
(so eviction is just ZREMRANGEBYSCORE, no separate expiry job needed to
read a correct count), and a repeat request from the same cart renews
its own hold instead of competing against the capacity gate again.
- app/routes/apps.scheduling.hold.tsx: public app-proxy endpoint the widget
calls the moment a shopper picks a slot — reserves capacity BEFORE the
cart attribute is written, since the attribute alone is just two
shoppers racing to write the same field.
- extensions/datetime-widget: now fetches the cart token, requests a hold
first, and only writes cart attributes on success; shows an error and
refreshes the slot list if it loses the race.
- app/services/booking.server.ts + webhooks.orders.create.tsx: converts an
order's dd_* cart attributes into a confirmed Booking (idempotent on
orderId — webhooks redeliver), releases the matching hold, and writes a
`delivery_datetime.booking` order metafield so the slot is visible on the
order record natively (IMPLEMENTATION_PLAN.md §5.2/§2). Deliberately does
NOT re-check capacity and reject at this point — by the time an order
exists, payment has happened; that's the Function's job, earlier.
- webhooks.orders.cancelled.tsx: marks the Booking cancelled, freeing its
capacity.
- extensions/validation-slot (Cart/Checkout Validation Function): blocks
checkout when the cart's dd_* attributes are missing or incomplete — a
shopper who bypasses the widget entirely (clears the attribute, calls the
cart API directly) still cannot check out, because this runs inside
Shopify's own checkout, not the browser. Scope note documented in
evaluate.js: this doesn't yet re-validate against a live capacity
snapshot at the moment checkout completes ("has since been taken" in
§3.1) — Functions can't call our DB, and a metafield-snapshot refresh
pipeline for that is unscoped work; the 10-minute hold TTL is the interim
mitigation for that specific race.
- extensions/delivery-customization: relabels every delivery option to the
shopper's actual chosen method + date ("Pickup — Aug 25" instead of a
generic carrier label), directly fixing the "estimated delivery date on a
pickup order" complaint §3.1 names. payment-customization is deliberately
NOT built yet — there's no configurable payment-method rule for it to
enforce until later phases add one; shipping a no-op Function serves
nothing.
- Prisma: added Booking (no SlotHold table — Redis is the sole source of
truth for holds, per §4's own "(Redis-backed)" annotation; mirroring it
into Postgres would just be a sync-consistency burden with no benefit).
- Both Function extensions are hand-scaffolded from Shopify's documented
JS Function structure (same interactive-login limitation as the Theme
App Extension) — README.md flags that `shopify app function schema`
should be run before deploying to confirm the input queries still match
the live schema.
New tests/integration/ suite (separate vitest config, needs live
Redis+Postgres — `docker compose up -d` locally, a services: block in CI)
holds the tests that can't be meaningfully mocked: the slot-hold
concurrency test CLAUDE.md calls out as non-negotiable (20 concurrent
requests for 1 unit of capacity → exactly 1 succeeds; 30 for 5 → exactly 5;
release-then-retry; TTL expiry; same-cart renewal) and the full
hold-to-booking lifecycle including idempotency on webhook redelivery.
Both Functions' pure decision logic is separately unit-tested (11 tests).
60 total tests now pass (52 unit + 8 integration).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
6c633af598 |
feat: Phase 3 — storefront widget + cart attributes
Theme App Extension (extensions/datetime-widget/) hand-scaffolded from
Shopify's documented structure, since `shopify app generate extension`
needs the same interactive Partner login `shopify app init`/`dev` do
(unavailable in this session) — user confirmed this approach.
- blocks/app-embed.liquid: site-wide toggle that loads the widget's JS/CSS
once (target: body)
- blocks/datetime-picker.liquid: the actual app block merchants add to a
cart/product page section, with per-method show/hide toggles and an
optional location override, all theme-editor-configurable
- src/datetime-widget.ts: vanilla TS (no framework, ~2kb gzipped) — renders
method -> date -> slot, calls the app-proxy availability endpoint, and on
selection writes to /cart/update.js cart attributes using a *method-
specific* attribute name ("Pickup date" vs "Delivery date" vs "Shipping
date", from locales/en.default.json) rather than one generic label — this
is the direct fix for the "estimated delivery date on a pickup order"
complaint PRODUCT_STRATEGY.md §3.1 calls out. Only collects a selection;
never enforces anything itself (Phase 4's Validation Function does that).
- locales/en.default.json + en.default.schema.json: i18n from day one
- app/routes/apps.scheduling.availability.tsx: the public app-proxy
endpoint the widget calls. Path is `apps.scheduling.availability` (not
the plan's suggested `api.availability`) because shopify.app.toml's
[app_proxy].url already includes the /apps/scheduling prefix, and
Shopify forwards a shop-facing /apps/scheduling/availability request to
{url}/availability against that full url — so the Remix route path has
to mirror the proxy path exactly for the forwarding to land correctly.
Resolves (location, method) -> DB rows -> getAvailability(), scoped by
shopDomain throughout. No consumption wired up (Booking doesn't exist
until Phase 4), so this correctly shows full capacity everywhere for now.
Theme App Extensions have no CLI build step, so `npm run build:widget`
(esbuild, added as a devDependency) bundles src/ into assets/ and is wired
as a predev/predeploy hook so `shopify app dev`/`deploy` never ship a stale
bundle. CI now also runs both `npm run build` and `npm run build:widget`.
Verified: lint, typecheck, unit tests, both builds pass; a live script
exercising the exact DB-query + getAvailability path the availability route
uses (bypassing HTTP, since real app-proxy signature verification needs a
live tunnel) returned correct results against the Postgres container —
correct EDT offset, correct capacity, and exactly the weekday-filtered set
of open dates for the seeded bakery template.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
0303eba07a |
feat: scaffold Phase 0 — Remix app template, CI, Redis/BullMQ, test harness
Bootstraps from Shopify's official shopify-app-template-remix (cloned directly rather than via `shopify app init`, which requires an interactive Partner login unavailable in this session): - Prisma (SQLite dev / Postgres-ready) with baseline Session model + migration - Vitest configured for unit tests, Playwright configured for E2E - Redis client + BullMQ worker skeleton (app/lib/redis.server.ts, jobs/worker.ts) - shopify.app.toml: minimal scopes, GDPR + orders webhooks wired (stub handlers), app proxy config for the future storefront widget - Stripped template-repo-only meta files (CLA, issue templates, demo product page) and replaced CI with a lint+typecheck+test workflow - Bumped @shopify/shopify-app-session-storage-prisma to resolve a duplicate @shopify/shopify-api install that broke typecheck - Dropped the Jest-only ESLint config (template default) since the project standardizes on Vitest per IMPLEMENTATION_PLAN.md Verified: npm install, lint, typecheck, unit tests, prisma migrate dev, and npm run build all pass on Node 22 LTS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |