Skip to main content

Sprint 1 / 2 / 3 — UI/E2E CRITICAL Execution Plan

Source audit: ui-e2e-audit-2026-04-26.md Sequencing: Sprint 1 (4 d) → Sprint 2 (5 d) → Sprint 3 (3 d) — total ~12 working days, single-engineer pace Mode: Each sprint commits + pushes per-item. Build + restart affected services. Add E2E spec where flagged. Update CLAUDE.md.


Sprint 1 — Demo-blocker CRITICALs (4 d)

Goal: every demo-blocking lie or stub is gone. After Sprint 1 a sales demo cannot be embarrassed by "this looks great but the button does nothing".

S1-Item 1 (CRITICAL-1) — Front Desk arrivals / departures / in-house tabs​

Effort: 0.75 d Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/front-desk/arrivals/keynova.tsx — replace placeholder strings with real tables
  • New: hmp-web-dashboard/src/components/modules/frontdesk/ArrivalsTable.tsx, InHouseTable.tsx, DeparturesTable.tsx
  • Optional: extend lib/hooks/use-reservations.ts if the existing query lacks date/status filters

Backend: already exists — GET /api/properties/:propertyId/{arrivals,departures,in-house}?date=YYYY-MM-DD. No backend work.

UI changes:

  • Tabs: Arrivals (CONFIRMED + arrival=today) | In-House (CHECKED_IN) | Departures (CHECKED_IN + departure=today) | No-Shows (CONFIRMED + arrival<today)
  • Each tab: data table with columns confirmationNumber, guestName, roomNumber, vipLevel, eta/etd, status, [primary action]
  • Primary actions per tab: Arrivals → "Check In"; In-House → "View Folio"; Departures → "Check Out"; No-Shows → "Process No-Show"
  • Empty state per tab with date-picker

E2E: New spec front-desk-arrivals-tabs.spec.ts

  • Click each tab, assert column headers visible
  • Assert ≥1 row when seed data has matching reservation
  • Click primary action button, assert URL navigation

Validation: Browser-test against rebuilt dashboard locally; seed has matching reservations.


S1-Item 2 (CRITICAL-4) — POS Email Bill button​

Effort: 0.25 d Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/pos/orders/[id]/page.tsx — add button next to "Print Receipt"
  • Edit: hmp-web-dashboard/src/lib/api/pos.ts — add emailBill(orderId, email, subject?) calling POST /api/orders/:id/email
  • Edit: hmp-web-dashboard/src/lib/hooks/use-pos.ts — useEmailBill() mutation hook

Backend: Already shipped (P1-B1, pos d66ae07). Endpoint POST /api/orders/:id/email accepts {email, subject?}. No backend work.

UI: Modal with email input (pre-filled from order.guestEmail), optional subject override, "Send" button. Toast on success: "Bill emailed to ". Disabled until order is BILLED or PAID.

E2E: New spec pos-email-bill.spec.ts

  • Open billed order, click Email Bill, fill modal, assert toast + notification log row visible

Validation: Direct curl confirms endpoint works (verified earlier this session).


S1-Item 3 (CRITICAL-5) — P0-12 guardian fields UI​

Effort: 0.5 d Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/crm/guests/... (CreateGuestDialog + EditGuest form)
  • Edit: hmp-web-dashboard/src/lib/validations/guest.ts — Zod schema gates guardian fields on dateOfBirth < 18
  • Edit: hmp-web-dashboard/src/types/guest.ts — add 4 fields

Backend: Already shipped (guest b2cbe59). DPDPA §9. No backend work.

UI:

  • Conditional section that appears when DOB indicates age < 18
  • Fields: guardianName (required), guardianRelation (select: PARENT / GUARDIAN / NEXT_OF_KIN), guardianPhone (E.164)
  • Hidden parentalConsentGiven checkbox flipped to true when guardian fields filled
  • Display guardianConsentAt (read-only) on edit form

E2E: New spec crm-guardian-fields.spec.ts

  • Create adult guest — succeeds without guardian fields
  • Create minor without guardian fields — form shows red errors
  • Create minor with all 4 fields — succeeds; detail page shows guardian section

Validation: Backend already enforces; flow test dpdpa-children-consent.flow.spec.ts passes 7/7.


S1-Item 4 (CRITICAL-6) — DSAR /crm/dsar page​

Effort: 0.75 d Files:

  • New: hmp-web-dashboard/src/app/(dashboard)/crm/dsar/page.tsx — list view with filters
  • New: hmp-web-dashboard/src/app/(dashboard)/crm/dsar/[id]/page.tsx — detail with status timeline + audit entries
  • New: hmp-web-dashboard/src/lib/api/dsar.ts
  • New: hmp-web-dashboard/src/lib/hooks/use-dsar.ts
  • Edit: hmp-web-dashboard/src/components/layout/sidebar.tsx — add "DSAR Requests" under CRM section

Backend: Already shipped (guest fe770da, P1-C4). Endpoints: POST /dpdp/requests, GET /dpdp/requests, GET /dpdp/requests/:id, PATCH /dpdp/requests/:id/status. No backend work.

UI:

  • List: trackingId, guest, requestType, status badge, slaDeadline (with "Xd remaining" + red if escalated), createdAt
  • Filters: status (PENDING/IN_REVIEW/COMPLETED/REJECTED), requestType (ACCESS/CORRECTION/ERASURE/NOMINATION), dateRange, search by trackingId/email
  • Detail: header (trackingId, status, sla countdown), timeline (audit entries chronological), actions (advance status, reject with reason)
  • "Export Data Bundle" action for ACCESS-typed requests (calls existing /gdpr/guests/:id/export)
  • "Erase Now" action for ERASURE — confirmation modal warns "irreversible"

E2E: New spec crm-dsar-lifecycle.spec.ts

  • Create ACCESS request → status PENDING → trackingId visible
  • Advance to IN_REVIEW → audit entry appears in timeline
  • Advance to COMPLETED → export bundle download triggered
  • Create ERASURE request → confirmation → guest anonymised

Validation: Backend confirmed via flow test dpdpa-children-consent.flow.spec.ts (7/7 pass).


S1-Item 5 (CRITICAL-11) — Settings → Branding logo upload​

Effort: 0.5 d Decision: Implement signed S3 upload (AWS already wired for property-service asset module).

Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/settings/branding/page.tsx line 177 — replace fake upload with real S3 PUT
  • Edit: hmp-web-dashboard/src/lib/api/uploads.ts — getSignedUploadUrl(kind, mimeType) calls POST /api/uploads/sign (already exists in property-service AssetModule)
  • Edit: hmp-web-dashboard/src/lib/api/settings.ts — updateBranding({logoUrl, faviconUrl, ...}) already exists; ensure URL is sent

Backend: property-service/src/assets/ already has AssetModule with S3 + sharp. Verify POST /api/uploads/sign is gateway-routed; if not, add it (two-file rule).

UI:

  • File input → call getSignedUploadUrl → S3 PUT → updateBranding({logoUrl: returned-url}) → toast "Logo updated"
  • Preview shows uploaded image; mismatch triggers re-upload prompt

E2E: Extended spec settings-branding.spec.ts

  • Mock signed URL response, mock S3 PUT 200, mock branding update — assert preview updates
  • (Real S3 integration test deferred — too heavy for E2E; smoke-test in flow tests suffices)

Fallback if /api/uploads/sign doesn't exist: Hide the upload UI behind a FLAGS.s3UploadsEnabled flag and surface a "Logo upload coming soon" message.


S1-Item 6 (CRITICAL-14) — Razorpay LIVE-key gate​

Effort: 0.5 d Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/payments/links/... — disable "Create Link" when active merchant account mode='TEST'
  • Edit: hmp-web-dashboard/src/lib/hooks/use-merchant-accounts.ts — expose activeAccountMode
  • Edit: payment-providers settings page — show prominent "TEST MODE" banner when no LIVE account
  • Edit: hmp-web-dashboard/src/components/modules/payments/PaymentLinkCreateDialog.tsx — block submit + show "Switch to LIVE Razorpay account before sending payment links to guests"

Backend: Already shipped (modular payments Phase 0). No backend work — MerchantAccount.mode already exists.

UI:

  • Banner on /payments/links and /settings/integrations/payment-providers: "⚠ Test mode — payments will not settle"
  • Disabled "Create Link" button with tooltip
  • Help link to docs explaining how to swap test → LIVE keys

E2E: New spec payment-link-mode-gate.spec.ts

  • Mock test-mode merchant account → assert banner + disabled state
  • Mock live-mode → assert button enabled + no banner

S1-Item 7 (CRITICAL-15) — /partners — fix or remove​

Effort: 0.75 d Decision: Hide from sidebar + 404 the route until the partner-organisation backend lands. KeyNova has a documented partner-org concept (per project_third-party-revenue-manager-model.md) but the implementation is paused. Surfacing hardcoded mock data is worse than hiding the page.

Files:

  • Edit: hmp-web-dashboard/src/components/layout/sidebar.tsx — remove "Partners" nav link
  • Replace: hmp-web-dashboard/src/app/(dashboard)/partners/page.tsx — render a "Partner module — coming Q3" placeholder instead of mock array
  • Document: add a (roadmap) annotation in the placeholder linking to project_rmc-partner-portal-status.md

E2E: Skip partners.spec.ts if it exists; add an assert that route returns the roadmap placeholder.

Validation: Manual smoke — sidebar no longer shows Partners.


Sprint 1 deliverables checklist​

  • 7 commits across 3-4 repos (dashboard mostly; possibly property-service if S3 sign endpoint needs work)
  • 7 new / extended E2E specs
  • All 7 services rebuilt + restarted on local Docker
  • Sidebar /partners removed
  • CLAUDE.md updated for affected pages
  • Sprint 1 closeout report committed

Sprint 2 — Banquet feature parity (5 d)

Goal: banquet ops can run a full event lifecycle on the dashboard end-to-end. Today they cannot.

S2-Item 1 (CRITICAL-7) — Function sheet CRUD​

Effort: 1.5 d Files:

  • New: hmp-web-dashboard/src/app/(dashboard)/banquet/events/[id]/function-sheet/page.tsx
  • New: hmp-web-dashboard/src/components/modules/banquet/FunctionSheetEditor.tsx — multi-section form
  • New: hmp-web-dashboard/src/lib/api/function-sheets.ts
  • New: hmp-web-dashboard/src/lib/hooks/use-function-sheets.ts
  • Edit: banquet event detail page — add "Function Sheet" tab with link

Backend: Already exists — GET/PUT /api/events/:eventId/function-sheet, POST /:eventId/function-sheet/approve. No backend work.

UI: Multi-section form with these JSON-backed fields:

  • Setup instructions (text)
  • Seating arrangement (select: theater / classroom / banquet / U-shape / boardroom + capacity field)
  • Menu details (select catering menu + servings count + dietary notes)
  • Beverage details (alcoholic / non-alcoholic toggles + bar service tier)
  • AV requirements (sound, video, projector, mic count, special equipment)
  • Timeline (table editor: time + event item + responsible)
  • Staff assignments (table editor: role + person + count)
  • Approve button (manager-role) flips approvedBy/approvedAt

E2E: New spec banquet-function-sheet.spec.ts

  • Create event → open function sheet → fill all sections → save → reload → assert persisted
  • Manager approves → version increments → audit entry visible

S2-Item 2 (CRITICAL-8) — Contract PDF generation​

Effort: 1.0 d Files:

  • Edit: banquet event detail page — add "Generate Contract" button (manager+) and "Download PDF" link once generated
  • Edit: hmp-web-dashboard/src/lib/api/banquet.ts — generateContract(eventId), downloadContract(eventId) callers
  • Edit: hmp-web-dashboard/src/lib/hooks/use-banquet.ts — useGenerateContract() mutation

Backend: Already exists — POST /api/events/:id/contract/generate, GET /api/events/:id/contract. Puppeteer Chromium runtime fix shipped this week.

UI:

  • "Generate Contract" button only enabled when event status is QUOTATION_SENT or later
  • Generation kicks off async job; show spinner until ready
  • Once generated, PDF download link + status badge: DRAFT / SENT / SIGNED
  • "Send for Signature" optional action (defer to next sprint if integration too heavy — for now, downloads PDF and marks SENT manually)

E2E: New spec banquet-contract-pdf.spec.ts

  • Create event → confirm → generate contract → assert URL returned + PDF accessible
  • Verify PDF mime-type and non-zero size

Validation: Smoke test against running stack — contracts.service.ts now uses Chromium properly.


S2-Item 3 (CRITICAL-9) — Banquet costing breakdown UI​

Effort: 1.0 d Files:

  • Edit: banquet event detail page — add "Costing" tab
  • New: hmp-web-dashboard/src/components/modules/banquet/EventCostingPanel.tsx
  • Edit: hmp-web-dashboard/src/lib/hooks/use-banquet.ts — useEventCosting(eventId) query

Backend: Already exists — GET /api/events/:id/costing (uses PosServiceClient.getMenuItem + getRecipeByMenuItem from this week's package). No backend work.

UI: Per-line-item breakdown:

  • For each FOOD/BEVERAGE EventItem with menuItemId → show ingredient cost / unit, total cost, food-cost %
  • For items without menuItemId → "Link to menu" CTA (uses S2-Item 4 picker)
  • Aggregate totals: total F&B revenue, total ingredient cost, overall food-cost %, target vs actual variance
  • Warning chip per line item when food-cost % > target (configurable, default 35%)

E2E: New spec banquet-costing.spec.ts

  • Event with linked menu items → open Costing tab → assert breakdown rows + totals
  • Event without linked items → assert "Link to menu" CTAs

S2-Item 4 (CRITICAL-10) — EventItem.menuItemId picker​

Effort: 1.0 d Files:

  • Edit: banquet event detail page — event-items table gets a "Linked Menu" column with link/edit affordance
  • New: hmp-web-dashboard/src/components/modules/banquet/MenuItemPickerDialog.tsx
  • Edit: hmp-web-dashboard/src/lib/api/banquet.ts — updateEventItem(eventId, itemId, {menuItemId})

Backend: Already exists — schema column shipped (banquet 84d5055); DTO accepts the field. No backend work.

UI:

  • Picker queries POS menu items via GET /api/outlets/:outletId/menu per outlet at the property
  • Search by name; group by category
  • Select → save → row updates with link icon + menu-item name
  • Unlink action available

E2E: Extend banquet-costing.spec.ts

  • From event detail, link a FOOD line item to a POS menu item
  • Costing tab now shows ingredient breakdown

S2-Item 5 (combined cleanup) — Banquet event payments add/edit dialogs​

Effort: 0.5 d (HIGH-9 from audit, folded in here because it touches the same UI surface) Files: event detail page payments section. UI: "Add Payment" + per-row "Edit" actions backed by POST/PUT /api/events/:eventId/payments. (Endpoints already exist.)


Sprint 2 deliverables checklist​

  • 5 commits in hmp-web-dashboard
  • 4 new E2E specs
  • Banquet event lifecycle navigable end-to-end via dashboard for the first time
  • CLAUDE.md updated
  • Sprint 2 closeout report

Sprint 3 — E2E gaps + integrations triage (3 d)

Goal: every demo path has E2E coverage; obviously-stub pages are gated behind feature flags or hidden.

S3-Item 1 (CRITICAL-2) — Folio settle flow E2E​

Effort: 0.5 d Files:

  • New: hmp-web-dashboard/e2e/tests/folio-settle.spec.ts

Coverage:

  • Open folio with non-zero balance → click Settle → modal warns "balance must be zero" → button disabled
  • Pay full balance → balance = 0 → click Settle → folio status flips to SETTLED → list shows SETTLED chip
  • After settle, "Post Charge" button disabled
  • Refund-after-settle path → CreditNote created (separate flow, soft-skip if endpoint absent)

Validation: Backend already correct per flow tests; this is pure E2E coverage.


S3-Item 2 (CRITICAL-3) — Reservations chip filter E2E​

Effort: 0.5 d Files:

  • Edit: hmp-web-dashboard/e2e/tests/15-crud-reservations.spec.ts — add chip click scenarios

Coverage:

  • Click "Today's Arrivals" chip → URL contains ?filter=today-arrivals → table filtered → row count > 0 (with seed)
  • Click "Today's Departures" → similar
  • Click "In-House" → similar
  • Clear filter → row count > previous

S3-Item 3 (CRITICAL-12) — SAP/Tally/WhatsApp integrations triage​

Effort: 1.0 d Decision: Gate behind FLAGS.integrationsEnabled (default OFF) + add "Coming Q3 — design partners only" banner.

Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/settings/integrations/sap/page.tsx, tally/page.tsx, whatsapp/page.tsx
  • Edit: hmp-web-dashboard/src/lib/feature-flags.ts — add integrationsEnabled flag
  • Edit: sidebar — gate the integrations sub-tree behind the flag

Behaviour when flag OFF (default):

  • Page renders the design (so design partners can see it) but disables every mutation button
  • Top banner: "This integration is in design-partner preview. Backend connectors arrive in Q3 2026. Contact ops@keynova.com to join the pilot."
  • All API calls replaced with console.warn('[integrations] disabled') + toast.

Validation: No demo lies. Sales can show the wireframes without the buttons silently 404'ing.


S3-Item 4 (CRITICAL-13) — SendGrid bounce/complaint UI surface​

Effort: 1.0 d Files:

  • Edit: hmp-web-dashboard/src/app/(dashboard)/crm/campaigns/[id]/metrics/... — add "Deliverability" tab
  • New: hmp-web-dashboard/src/components/modules/crm/DeliverabilityPanel.tsx
  • Edit: hmp-web-dashboard/src/lib/api/notifications.ts — getDeliverabilityStats(campaignId, dateRange)
  • Edit: notification-service — add GET /api/notifications/deliverability?campaignId&from&to returning bounce/spam/unsubscribe counts grouped by reason

Backend: Webhook receiver already shipped (notification 4ba8a91); only the aggregation endpoint is new.

UI:

  • Top KPIs: delivered, opened, clicked, bounced, marked-spam, unsubscribed
  • Drill-through table: per-recipient with status, bounce reason, timestamp
  • "Re-send to soft-bounces" action (calls existing send mutation with retry flag)

E2E: New spec crm-campaign-deliverability.spec.ts

  • Trigger a campaign → simulate SendGrid webhook with bounce event → assert bounce KPI increments + recipient row appears

Sprint 3 deliverables checklist​

  • 4 commits (notification-service for new endpoint, dashboard for the rest)
  • 4 new E2E specs (folio-settle, reservations chips, integrations gate, campaign deliverability)
  • FLAGS.integrationsEnabled wired
  • CLAUDE.md updated
  • Sprint 3 closeout + final cumulative report

Cumulative results after Sprint 1+2+3

TierClosedRemaining
CRITICAL15 / 150
HIGH4 (folded in Sprints 1+2)13
MEDIUM024
LOW03

Net effect: dashboard goes from "demo-blocking lies in 7 places" → "every CRITICAL feature is real and tested". HIGH list becomes the next quarter's roadmap; MEDIUM is operational backlog.


Risks + assumptions

  1. S3 sign endpoint (S1-Item 5) — assumed to exist in property-service AssetModule. If not, falls back to feature-flag-gated UI. Verify before starting.
  2. Banquet contracts module backend signature flow — out of scope; we ship the PDF generation only. Signature integration deferred to next quarter.
  3. Notification-service deliverability endpoint (S3-Item 4) — net-new backend work, ~3 hours. Schema fields already exist on NotificationLog.
  4. Pretest-reset wipes seed — flow tests for these features will need targeted seed-rebuild between runs. Flagged in the existing audit.

Approval gate

Before I start Sprint 1:

  • Confirm S3 sign endpoint exists OR approve fallback for CRITICAL-11
  • Confirm /partners should be hidden (vs. implemented OR repurposed)
  • Approve "Coming Q3" framing for SAP/Tally/WhatsApp integrations
  • Approve sequence S1 → S2 → S3 (no parallelization)

If you approve as-is, I'll start S1-Item 1 immediately and report after each item lands.