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.tsif 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— addemailBill(orderId, email, subject?)callingPOST /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
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 ondateOfBirth < 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
parentalConsentGivencheckbox 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.tsxline 177 — replace fake upload with real S3 PUT - Edit:
hmp-web-dashboard/src/lib/api/uploads.ts—getSignedUploadUrl(kind, mimeType)callsPOST /api/uploads/sign(already exists in property-serviceAssetModule) - 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 accountmode='TEST' - Edit:
hmp-web-dashboard/src/lib/hooks/use-merchant-accounts.ts— exposeactiveAccountMode - 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/linksand/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 toproject_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
/partnersremoved - 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/menuper 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— addintegrationsEnabledflag - 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&toreturning 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
| Tier | Closed | Remaining |
|---|---|---|
| CRITICAL | 15 / 15 | 0 |
| HIGH | 4 (folded in Sprints 1+2) | 13 |
| MEDIUM | 0 | 24 |
| LOW | 0 | 3 |
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
- 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. - Banquet contracts module backend signature flow — out of scope; we ship the PDF generation only. Signature integration deferred to next quarter.
- Notification-service deliverability endpoint (S3-Item 4) — net-new backend work, ~3 hours. Schema fields already exist on NotificationLog.
- 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
/partnersshould 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.