Implements the "heart" of the app per IMPLEMENTATION_PLAN.md §6 Phase 2,
no UI yet by design:
- app/lib/time.ts: Luxon-based wall-clock helpers. slotDateTime() builds a
slot's instant from calendar-date + minutes-from-midnight by setting
hour/minute fields directly rather than adding an elapsed-time duration
to midnight — the latter is wrong by exactly one hour for any wall-clock
time on a spring-forward day, since a real elapsed-time addition crosses
the lost hour. Also detects and rejects wall-clock times that don't exist
in a spring-forward gap (Luxon silently rolls these forward instead of
invalidating them, so this required an explicit post-set field check).
- app/services/scheduling.server.ts: getAvailability(), a pure function
(no DB/Shopify calls — every input injected, including `now`) that
applies slot templates, date overrides, blackout dates, cutoff/lead-time,
and capacity to produce the bookable slots per date. Excludes unavailable
slots entirely rather than flagging them, per PRODUCT_STRATEGY.md §2.
- app/services/capacity.server.ts: remainingCapacity()/hasCapacity() — kept
as pure functions over an injected `consumed` count so this doesn't need
to change once Phase 4 adds real Booking/SlotHold-backed consumption.
41 unit tests total (up from 8), including DST regression tests that would
fail against a naive "midnight + elapsed minutes" implementation, an
ambiguous-time (fall-back) case, a nonexistent-time (spring-forward gap)
case, and a getAvailability run across the actual 2024 spring-forward date
verifying both the UTC offset change and that wall-clock hours stay correct
on both sides of it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the scheduling core to Prisma (Shop, Location, Method enum,
SlotTemplate, SlotOverride, BlackoutDate — IMPLEMENTATION_PLAN.md §4) and
Polaris admin screens to manage them:
- /app/locations: list/create/edit/delete locations, one-click vertical
template seeding (bakery/florist/grocer)
- /app/slots: weekly slot template editor, scoped per location
- /app/blackouts: blackout dates, scoped to one location or all of them
services/templates.server.ts keeps the vertical presets as a pure,
unit-tested function (getVerticalTemplate) separate from the thin I/O
wrapper (seedVerticalTemplate) that does the actual Prisma writes, per
CLAUDE.md's "pure functions, inject data, no I/O in the math" rule.
Switched dev DB from SQLite to Postgres (docker-compose.yml, ports 5433/6380
to avoid clashing with other local projects already on 5432/6379): SQLite
doesn't support Prisma enums at all, and IMPLEMENTATION_PLAN.md's schema
relies on them (Method) plus Postgres-only array fields in later phases
(Zone.postalCodes, ProductRule.allowedLocationIds). Only one throwaway
migration existed, so switching now avoids compounding the rework later.
Also fixed a Remix/Polaris integration issue hit while building these forms:
this Polaris version's TextField/Checkbox are fully controlled (no
defaultValue/defaultChecked), and `data()` imported from `@remix-run/react`
(rather than `@remix-run/node`) breaks useActionData's type inference —
both are now handled correctly across the new routes.
Verified: lint, typecheck, unit tests (incl. template-seeding fixtures),
build, and a live end-to-end run of seedVerticalTemplate against the
Postgres container all pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>