Skip to main content

EP-011: Booking Engine — Phase 1 Hardening

Phase 1 hardening sprint, 2026-04. Largest hardening footprint of any epic. Branches across hmp-reservation-service, hmp-property-service, hmp-shared-packages, and hmp-booking-engine. Not yet pushed.

The booking engine had four shipping items in Phase 1: a complete xPrisma → ServiceClient migration (19 sites, the biggest single-epic migration in the sprint), mobile responsive polish, the introduction of standard loading/empty/error state primitives, and a WCAG 2.1 AA accessibility pass.


C4b — Booking-Engine xPrisma → ServiceClient Migration​

Branch: feat/booking-engine-serviceclient (in hmp-reservation-service, hmp-property-service, hmp-shared-packages)

What was fixed​

The reservation service's booking engine code path read directly from roomType, room, ratePlan, and property models that don't exist in its Prisma schema. This was the root cause of multiple production crashes during seed runs in March 2026.

All 19 xPrisma sites migrated. Eight new endpoints introduced on property-service to own the reads:

New endpointReplacesxPrisma model
GET /properties/:id/booking-engine/availability-snapshotavailability.service.ts snapshot logicroomType, room, roomReservation
GET /properties/:id/booking-engine/room-typesavailability.service.ts room-type listroomType
GET /properties/:id/booking-engine/rate-planscart.service.ts rate-plan listratePlan
GET /properties/:id/booking-engine/rate-plans/:id/with-ratesrate-plans.service.ts rate expansionratePlan, dailyRate
GET /properties/:id/booking-engine/room-status-countsavailability.service.ts HK widgetroom
GET /properties/:id/booking-engine/room-countcapacity-ceiling checkroom count
GET /properties/:id/booking-engine/available-roomcart.service.ts room bindingroom
GET /properties/:id/booking-engine/room-types-with-rate-plansinitial-load convenienceroomType + ratePlan

Full API spec: api/booking-engine-support-api.md.

CRITICAL security fix​

Every new endpoint enforces assertPropertyTenant(propertyId, tenantId) before serving data. This is a single SELECT cached for 5 minutes that verifies the requested property is owned by the claimed tenant. The old xPrisma code did not perform this check on the receiver side, which means a service-account caller could in theory have requested data from any property. This is now closed.

PropertyServiceClient extensions​

Eight new methods added in @hmp/service-clients/PropertyServiceClient. All use AsyncLocalStorage for tenant/property forwarding and inherit the INTERNAL_SERVICE_TOKEN injection from BaseServiceClient. See architecture/cross-service-communication.md for the full pattern.

Files touched​

  • hmp-reservation-service/src/availability/availability.service.ts (5 sites)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts (8 sites)
  • hmp-reservation-service/src/cart/cart.service.ts (4 sites)
  • hmp-reservation-service/src/rate-plans/rate-plans.service.ts (2 sites)
  • hmp-property-service/src/booking-engine-support/ (new module: controller, service, module, 8 controller methods, 8 service methods, 8 DTOs)
  • hmp-property-service/src/booking-engine-support/booking-engine-support.spec.ts (24 tests)
  • hmp-shared-packages/packages/service-clients/src/PropertyServiceClient.ts (8 methods)

Tests​

24 new property-service controller tests covering all 8 endpoints, success and error paths including the assertPropertyTenant failure case. All passing.


D2 — Booking Engine Mobile Responsive​

Branch: feat/booking-engine-mobile-polish (in hmp-booking-engine)

What was fixed​

  • New Sheet primitive (Radix UI Dialog wrapped as a bottom drawer)
  • New useMediaQuery hook for breakpoint-aware rendering
  • BookingSummary rendered as a bottom drawer on mobile (vs. sidebar on desktop)
  • Payment method selector becomes a bottom sheet on mobile
  • Sticky payment summary at the bottom of the viewport on mobile
  • All interactive controls hit the 44×44 minimum touch target
  • All input fields use text-base (16px) to disable iOS auto-zoom
  • Calendar cells are 44×44 minimum on mobile
  • Carousel images lazy-load and use srcSet for retina

Tests​

  • Playwright mobile suite: 23/23 pass
  • Playwright desktop suite: 18/18 pass
  • Playwright tablet suite: 18/18 pass
  • 8 visual regression screenshots captured

D3 — Booking Engine Empty/Loading/Error States​

Branch: feat/booking-engine-ui-states (in hmp-booking-engine)

What was added​

Three new shared primitives in src/components/common/:

  • LoadingState.tsx — spinner + label
  • EmptyState.tsx — icon + heading + body + optional action
  • ErrorState.tsx — icon + heading + retry action

Wired into 9 consumers (room list, rate panel, payment methods, search results, manage booking, etc.). 17 new unit tests covering rendering, ARIA semantics, and action callbacks. Mobile E2E suite still passes (24/24).


D7 — WCAG 2.1 AA Accessibility (Booking Engine)​

Branch: feat/booking-engine-a11y-fixes (in hmp-booking-engine)

What was fixed​

  • <html lang> attribute now syncs with i18next's active language
  • PaymentMethods component uses an ARIA radiogroup with proper aria-checked semantics
  • ProgressIndicator is a semantic <ol> instead of a div stack
  • aria-labels added to all icon-only buttons
  • Screen-reader-only status prefixes added to dynamic regions
  • Muted-foreground color tokens darkened to pass AA contrast

Known concerns​

  • Gold-on-ivory contrast failure: #B8962E (brand gold) on #FAF8F0 (ivory) measures 2.97:1, which fails AA (needs 4.5:1 for normal text). This affects accent text on the light theme. Design system decision needed — flagged in project_phase2-phase3-scope.md.

Summary​

StoryStatus beforeStatus afterNotes
US-011-01 (Room Search)PartialBuiltC4b: xPrisma fix unblocked happy path
US-011-02 (Rate Display)PartialBuiltC4b: rate plans now load
US-011-04 (Cart)PartialBuiltC4b: room binding works
US-011-06 (Payment Flow)PartialPartialD2/D7 polish; live Razorpay still pending
US-011-08 (Mobile Layout)Not StartedBuiltD2: full mobile responsive polish
US-011-12 (Loading States)Not StartedBuiltD3: state primitives shipped
US-011-15 (Accessibility)Not StartedPartialD7: 25 fixes; gold-on-ivory still fails

Story-level status promotions: 5.