Skip to main content

05 — Frontend Workflows and User Journeys

Status: populated Last updated: 2026-04-23 Scope: the canonical end-to-end UI workflows across every Ghasi Melmastoon surface — Consumer Meta Web, Tenant Booking Web, Consumer Mobile, Electron Desktop Backoffice, and the Control Plane. Each workflow declares its actors, role-based variants, services touched, state model, offline fallback, AI affordances, and the journey ID (J-NN) and epic ID (EP-MEL-NN) it realizes.

Companions: ../README.md · 01-product-overview-frontend.md · 02-architecture-overview-frontend.md · 06-theming-and-tenant-config.md · 03-design-system.md · 04-frontend-design-guidelines.md · ../desktop/21-desktop-app-specification.md · ../journeys/README.md · ../../07-epics-and-user-stories.md · ../../04-event-driven-architecture.md · ../../08-ai-architecture.md

This document is the frontend-perspective view of the platform's operational atlas. The cross-service operational atlas — events emitted, sagas, data changes per step — lives in ../journeys/README.md (22 per-journey files; the former docs/journeys/01-core-user-journeys.md is a redirect stub). This document focuses on the UI side of those journeys: which screens render, what state machines drive them, which BFF calls fire, what role variants exist, how each step degrades offline, and which AI affordances surface.


1. Critical-path workflow map

Twelve workflows cover every persona and every surface. Every workflow is an E2E gate — regression on any of them blocks release.

IDWorkflowPrimary surface(s)Journey linkEpic linkE2E gate
W-01Guest meta discovery — search, filter, compare, detailConsumer Meta Web · Consumer Mobile (Discover tab)J-01EP-MEL-02Yes
W-02Consumer → tenant handoff (signed token, tenant bootstrap, theme injection)Consumer Meta → Tenant BookingJ-02EP-MEL-02 + EP-MEL-03Yes
W-03Tenant booking funnel — multi-step with cash-on-arrivalTenant Booking Web · Consumer Mobile (Booking stack)J-03EP-MEL-03 + EP-MEL-04 + EP-MEL-05Yes
W-04Tenant booking funnel — card payment with 3DS + FX snapshotTenant Booking Web · Consumer MobileJ-04EP-MEL-03 + EP-MEL-04 + EP-MEL-05Yes
W-05Front-desk arrival — check-in, key issuance (mobile-key / PIN / encoded card)Electron Desktop BackofficeJ-05, J-09EP-MEL-06 + EP-MEL-09Yes
W-06Stay management — modify, mid-stay issues, check-out, folio closeElectron Desktop BackofficeJ-06, J-11EP-MEL-06 + EP-MEL-04Yes
W-07Walk-in offline booking with cash deposit (full offline path)Electron Desktop Backoffice (offline)J-07EP-MEL-06 + EP-MEL-10Yes
W-08Housekeeping coordination + maintenance escalationElectron Desktop Backoffice (incl. housekeeper kiosk sub-mode) · Consumer Mobile (mid-stay request)J-19, J-20, J-21, J-22EP-MEL-07 + EP-MEL-08Yes
W-09AI-assisted operations — pricing suggestions, daily ops dashboard, anomaly inboxElectron Desktop BackofficeJ-15, J-16EP-MEL-11Yes
W-10End-of-day cash drawer close + regulatory submission (tax / guest registration)Electron Desktop Backoffice (print + export)J-12, J-17, J-18EP-MEL-05 + EP-MEL-06Yes
W-11Tenant onboarding & theming — author / preview / publish / rollbackControl Plane (Phase 2) + Backoffice authoring consoleJ-13, J-14EP-MEL-01Yes
W-12Sync recovery & conflict resolution after offline windowElectron Desktop Backoffice (Sync Center)cross-cutting (J-07, J-20, J-12 reconnect tail)EP-MEL-10Yes

2. Cross-workflow conventions

These apply to every workflow and are not repeated per-workflow.

  • Tenant context is on every BFF call. X-Tenant-Id (header) + tid (JWT) cross-checked at the gateway. Any mismatch surfaces a generic "session expired" page; never raw error.
  • Idempotency on every mutation. Every POST/PUT/PATCH carries Idempotency-Key (ULID); user-retried clicks are no-ops, never duplicates.
  • Provenance on every AI step. Every AI suggestion renders model · version · prompt · traceId · local|cloud; HITL accept on irreversible mutations.
  • Audit-link in every operator action. State-changing toasts on the desktop expose a "View audit" link for 10 s pointing at the audit-log row.
  • Sync Status Pill is always visible on the desktop. Pending mutations and connectivity state are reflected at every step.
  • Telemetry events (frontend.<surface>.<verb>) emit per material step; backend domain events emit per service per docs/04-event-driven-architecture.md §3. PII never enters telemetry payloads.
  • RTL/LTR parity is rendered for every workflow; visual regression covers both directions per primary locale.
  • Locale fallback chain: requested → tenant default → platform en-US. Missing translations never render the raw key.
  • Confirmation surfaces (booking confirmed, payment captured, key issued, refund approved, regulatory submission accepted) are screen-reader-readable first, printable, and locale-multilingual where applicable.

W-01 — Guest meta discovery

Actors. Guest (anonymous; returning guest with session cookie). Surfaces. Consumer Meta Web (apps/web-meta), Consumer Mobile Discover tab (apps/mobile). Primary BFF. bff-consumer-service. Services touched (via BFF). search-aggregation-service · property-service · pricing-service · file-storage-service · notification-service (ratings projection). Realizes. J-01 · EP-MEL-02.

State diagram

Step-by-step

#StepUI surfaceBFF callCacheableOffline fallbackAI
1Search submit (location, dates, occupancy, price band, amenities)MetaSearchHeroPOST /search15 s edge, query-keyedLast-good results from PWA / MMKV cache; "Showing recent results" banner
2Render results list (20 cards)MetaResultsList(response of step 1)yesCached page render
3Apply filters (halal kitchen, prayer room, …)filter drawer (mobile) / sidebar (web)POST /search (re-issued)yesFilters apply over cached set; flagged "may be incomplete"
4Toggle map viewMetaResultsMap (Leaflet web / react-native-maps mobile)POST /search/map15 s edgeCached pins from last query
5Open compare drawer (≤ 3)MetaCompareDrawerGET /search/compare?ids=…session cacheFrom cached detail responses
6Open property detailPropertyDetailScreenGET /hotels/{id} + GET /hotels/{id}/availability60 s edgeLast-seen detail (up to 7 days)
7Save to wishlistwishlist icon on cardPOST /wishlistno (cookie-keyed)Mobile: queued in MMKV, replays on reconnect; web: blocked offline with banner
8Tap "Book on hotel site"listing-card CTA→ handoff (W-02 step 1)

Role variants

PersonaVariant
Anonymous guestDefault flow; wishlist persists per cookie
Returning guest (session cookie)"Resume search" pill from session storage; recently-viewed strip; wishlist hydrated server-side
Mobile vs webMobile uses bottom-sheet filters and bottom-tab navigation; web uses left-rail filters and top-bar

Offline fallback

  • Web (PWA). Service worker serves the cached app shell + last successful results page from IndexedDB. New searches are queued client-side and re-run on reconnect; the user sees an "Offline — cached results" pill.
  • Mobile (MMKV). Last 50 search results + last 20 viewed properties cached. Wishlist additions queued; replayed in FIFO on reconnect with idempotency keys.
  • Cannot work offline. Handoff (requires server-signed token), live availability (must be authoritative).

Telemetry

frontend.meta.search_submitted · frontend.meta.results_rendered { count, latency_ms } · frontend.meta.filter_applied · frontend.meta.compare_opened { ids } · frontend.meta.property_viewed { id } · frontend.meta.handoff_initiated { tenantId, propertyId }.


W-02 — Consumer → tenant handoff

Actors. Guest (transitioning from meta to tenant context). Surfaces. Consumer Meta → Tenant Booking Web · Consumer Mobile (Discover tab → Booking stack). Primary BFF. bff-consumer-service (issues handoff) → bff-tenant-booking-service (consumes handoff, emits bootstrap). Services touched. theme-config-service (active ThemeVersion) · tenant-service (status check) · pricing-service (currencies) · payment-gateway-service (enabled methods) · policies-service. Realizes. J-02 · EP-MEL-02 + EP-MEL-03.

State diagram

Step-by-step

#StepUI surfaceBFF callNotes
1Issue signed handoff (HMAC-SHA256, single-use, 5 min TTL)spinner overlay on listing cardPOST /handoff/{tenantSlug}/{propertyId} (consumer BFF)Body: { checkIn, checkOut, occupancy, locale, currency, sourceCampaign?, traceId }
2Navigate to tenant URLWeb: 302 to https://<slug>.melmastoon.app/book?h=<token>; Mobile: Linking.openURL (Universal / App Link)Mobile falls back to web if app not installed
3Tenant bootstrap (SSR layout)BootstrapTenantScreen (mobile) / Next.js layout (web)POST /handoff/consumeGET /bootstrapInjects theme tokens at SSR; no hydration flicker
4Enter funnel pre-populatedfirst booking step (DatesScreen or RoomChoiceScreen if dates known)(continues into W-03 or W-04)

Role variants

ScenarioVariant
Direct landing (no meta, user types <slug>.melmastoon.app/book)Same /bootstrap flow without consume; user picks dates/guests in step 1
Mobile deep linkUniversal Link / App Link; if app not installed → web URL fallback
Tenant suspendedGeneric "This hotel isn't accepting direct bookings right now" page; CTA to return to meta
Theme version unpublished mid-deployFalls back to last-known-good ThemeVersion; internal alert fires to tenant GM via desktop notification feed

Offline fallback

Online-only. Handoff requires authoritative server signature; tenant bootstrap requires authoritative theme version. If offline at the moment of handoff, the listing-card CTA is disabled with a "You're offline. Try again when connected." tooltip.

Telemetry

frontend.tenant.booking_session_started { tenantId, source } · frontend.tenant.theme_loaded { themeVersion, latency_ms } · frontend.tenant.handoff_invalid { reason }.


W-03 — Tenant booking funnel (cash-on-arrival)

Actors. Guest. Surfaces. Tenant Booking Web (apps/web-tenant-booking) · Consumer Mobile Booking stack (apps/mobile). Primary BFF. bff-tenant-booking-service. Services touched (via BFF). reservation-service (quote, hold, confirm) · inventory-service (availability, allocations) · pricing-service (FX snapshot) · payment-gateway-service (cash intent) · lock-integration-service (deferred key schedule) · notification-service. Realizes. J-03 · EP-MEL-03 + EP-MEL-04 + EP-MEL-05.

State diagram

Step-by-step

#StepUI screenBFF callOffline fallbackAI
1Select datesDatesScreen (mobile) / inline header (web)GET /availability (debounced)Read-only cached availability; "Cannot book offline" banner blocks Continue
2Choose room typeRoomChoiceScreenGET /availability (cached)Cards from cacheRecommended-room hint (opt-in)
3Upgrades / rate planUpgradesScreen(uses bootstrap rate-plan add-ons)Read-only
4Guest details (bilingual capture)GuestDetailsScreenPATCH /draft/{id} (debounced 500 ms)Online-only writes (PII protection)Transliteration suggestion (ai-orchestrator-service.transliterateName) — HITL accept/edit
5Review (summary + holds)ReviewScreenPOST /quotePOST /draft/{id}/hold (idempotency-keyed)Online-only
6Payment selection = Cash on ArrivalPaymentScreen(no /payment-intent call for cash; advances to confirm)Online-only
7ConfirmConfirmationScreen after POST /draft/{id}/confirmGET /confirmation/{rsv}
8Multilingual confirmation + voucherConfirmationScreen (with PDF download + Add-to-Calendar + share link)notification-service dispatches SMS / WhatsApp / email

Role variants

VariantUI difference
Single room vs multi-roomMulti-room shows occupancy stepper plus per-room guest details
Group booking (≥ 5 rooms)Auto-routes to W-06 group folio path; folio is single
Sharia-compliant tenantCard payment hidden; only cash-on-arrival + Hawala-mediated bank transfer surfaces
requireGuestSignature: trueAdds signature pad before confirm (canvas web / native pad mobile)
captureGuestPassport: true (AF tax-resident)Passport / Tazkira fields surfaced as required in GuestDetailsScreen
Mobile vs webMobile uses vertical-stepper (one screen per step); web defaults to vertical-stepper but supports single-page preset per theme.layoutPresets.booking

Hold-expiry handling

bootstrap.flowConfig.holdTtlSeconds drives a per-mount countdown. T-60 s: non-blocking toast warning. T-0: HoldExpiredBanner surfaces; "Re-quote with current availability" restarts at step 2. Idempotency keys ensure double-click on Confirm doesn't double-book.

Offline fallback

Online-only. The funnel is gated on authoritative inventory + pricing + payment-method state. Each step disables its CTA with a banner when offline. Already-rendered cached availability is shown as read-only context but cannot be acted on.

Telemetry

frontend.booking.quote_requested · frontend.booking.quote_received { latency_ms } · frontend.booking.method_picked { method: "cash_on_arrival" } · frontend.booking.confirm_clicked · frontend.booking.confirmed { reservationId } · KPI: direct-booking share, meta→booking conversion, time-to-confirm p95.


W-04 — Tenant booking funnel (card payment + 3DS)

Actors. Guest. Surfaces. Tenant Booking Web · Consumer Mobile. Primary BFF. bff-tenant-booking-service. Services touched. reservation-service · inventory-service · pricing-service (FX snapshot) · payment-gateway-service (card intent + 3DS + capture) · lock-integration-service · notification-service. Realizes. J-04 · EP-MEL-03 + EP-MEL-05.

Sequence (frontend perspective)

UI rules

  • Total displayed twice on PaymentScreen: AFN primary (settlement), USD secondary (card billing currency); FX-snapshot tooltip shows provider · capturedAt · ttlExpiresAt.
  • Stripe Elements iframe is the only place card data is entered; the SPA never sees PAN.
  • 3DS modal is the issuer's; the SPA only handles the post-redirect redirectResult token via POST /draft/{id}/return.
  • Idempotent /return — repeated calls return kind: 'already_confirmed'. Refresh after 3DS does not double-charge.
  • FX-snapshot is frozen at confirm. What the user saw on PaymentScreen is what is captured; ConfirmationScreen re-renders the same snapshot.

Failure paths

FailureUI behaviourRecovery
Card declinedIssuer code mapped to plain language ("Your card was declined. Try another card or pay cash on arrival.")Hold remains valid until TTL; user retries or switches to cash
3DS abandonedBanner: "Authentication not completed"Retry from PaymentScreen
Stripe webhook delayedOptimistic "Confirming…" with skeleton; if > 10 s, escalate to "Still confirming — we'll email you when done"Background poll on GET /confirmation/{rsv}
FX snapshot stale before confirmNon-blocking banner "Prices updated — refresh quote?"Refresh re-quotes; previously held price honored if hold still valid
Webhook fires "captured" but /return failsAudit shows captured payment without confirmed reservation; ops alert via audit-serviceSaga compensation: refund or manual reconciliation

Offline fallback

Online-only. All steps require authoritative payment-gateway state.

Telemetry

frontend.booking.payment_method_picked { method: "card" } · frontend.booking.3ds_started · frontend.booking.3ds_completed { latency_ms } · frontend.booking.payment_captured · frontend.booking.confirmed { reservationId }.


W-05 — Front-desk arrival (check-in + key issuance)

Actors. Front-Desk Clerk (primary), General Manager (override authority), Guest (presents at desk). Surfaces. Electron Desktop Backoffice (apps/desktop-backoffice). Primary BFF. bff-backoffice-service. Services touched. reservation-service · billing-service (folio open / charge / settle) · payment-gateway-service · lock-integration-service (mobile-key invite / PIN issue / encoded card) · notification-service · audit-service. Realizes. J-05, J-09 · EP-MEL-06 + EP-MEL-09.

State diagram

Step-by-step

#StepUI surfaceBFF callOffline fallbackAI
1Open arrivals grid (today)ArrivalsToday moduleGET /arrivals/todayReads from local SQLite; sync pill shows last sync timeRecommended-arrival-order hint
2Select reservation (scan QR / search / pick)row-action on gridGET /reservations/{id}Reads from local SQLite
3Verify identity (passport / Tazkira / NID)IdentityVerifyDialog (capture document, optional photo)PATCH /reservations/{id}/identityCaptured to local outbox; replays on reconnectOCR document scan (edge ONNX)
4Open folioFolioPanelPOST /folios/{rsvId}/openLocal outbox
5Pre-auth incidentals (card on file)inline action on folioPOST /folios/{id}/preauthOnline-only (vendor authoritative)
6Confirm room assignment (or reassign)RoomAssignmentPanelPOST /reservations/{id}/assignLocal outbox; conflict on reconnect → Sync CenterOptimal-room suggestion (proximity, party size, accessibility)
7Issue key — mobile-keyKeyIssuanceWizard (vendor: mobile-key)POST /key-credentials (vendor adapter)Local fallback to PIN if vendor offline; mobile-key issued on reconnect
7aIssue key — encoded cardwizard step (USB/serial encoder)POST /key-credentialsLocal issuance via encoder driver (no internet needed for encoder itself)
7bIssue key — PINwizard stepPOST /key-credentialsLocal PIN generation; pushed to lock on reconnect
8Welcome message dispatchtoast + inbox entrynotification-service outboundQueued in outboxOptional draft welcome (HITL)
9Mark checked-inreservation status pill flipsPOST /reservations/{id}/check-inLocal outbox; status reflected immediately

Role variants

PersonaVariant
Front-Desk ClerkFull check-in + key issuance; folio charges within scope; refund requires GM
General ManagerAll Front-Desk + override authority (rate, refund, room assignment outside ABAC)
Owner (read-only)Reads arrivals/in-house; cannot mutate
Compliance OfficerReads identity verification capture for audit; cannot issue keys

Offline fallback

Full offline. Identity verification, room assignment, PIN issuance, encoded card issuance all work offline against local SQLite + USB/serial encoder driver. Mobile-key issuance falls back to PIN when the vendor cloud is unreachable; the mobile-key credential is queued for issuance on reconnect, and the guest receives a push update at that point. Sync Status Pill always reflects the gap.

AI affordances

  • Document OCR runs locally via ONNX Runtime Node on the desktop's AI worker (no PII leaves the device).
  • Optimal-room suggestion considers proximity (other party rooms), accessibility flags, and noise (rooms near elevators / kitchens) — surfaced as <AISuggestionCard /> next to the room assignment panel.
  • Welcome message draft is HITL — clerk reviews before send.

Telemetry

frontend.desktop.arrivals_opened · frontend.desktop.checkin_started { reservationId } · frontend.desktop.identity_verified { method } · frontend.desktop.key_issued { vendor, fallback?: 'pin'|'encoded' } · frontend.desktop.checkin_completed { reservationId, latency_ms }.


W-06 — Stay management (modify, mid-stay, check-out)

Actors. Front-Desk Clerk · GM · Housekeeping Lead (informed) · Maintenance Tech (escalations) · Guest. Surfaces. Electron Desktop Backoffice; Consumer Mobile Manage tab (read-only + mid-stay request). Primary BFF. bff-backoffice-service (operator) · bff-tenant-booking-service (guest manage). Services touched. reservation-service · billing-service · payment-gateway-service · housekeeping-service (touchpoints) · maintenance-service (lock battery, plumbing) · lock-integration-service (re-issue / revoke) · notification-service · audit-service. Realizes. J-06, J-11 · EP-MEL-06 + EP-MEL-04 + EP-MEL-08.

Sub-workflows

SubTriggerStepsOffline
Modify dates / occupancyGuest request via desk; in-house grid actionRe-quote (POST /reservations/{id}/modify) → re-hold inventory → re-confirm; folio adjusted; FX delta surfaced explicitlyOnline-only (inventory authoritative)
Add charge to folio (incidentals, F&B, laundry)Folio panel "Add charge"POST /folios/{id}/charges (idempotency-keyed)Local outbox; replays
Lock battery dies mid-stay (J-09)Lock vendor heartbeat alarmMaintenance ticket auto-created; clerk re-issues credential (PIN fallback); maintenance escalatedLocal PIN fallback works offline
Folio dispute / late checkout (J-11)Manager triggers from folio panelGM override + reason code → folio adjustment → audit eventLocal outbox
Check-outFolio "Check out" CTA after settlementPOST /reservations/{id}/check-out → final folio close → key revocation → housekeeping task auto-created (W-08)Local outbox; key revocation queued
Revoke keySecurity incident, lost mobile, disputeKeyIssuanceWizard "Revoke" pathLocal revocation written to lock vendor outbox

Step-by-step (check-out path)

#StepUIBFFOfflineAI
1Select in-house reservationInHouseGridGET /in-houselocal SQLite
2Open folioFolioPanelGET /folios/{id}local
3Settle remaining balanceSettleDialog (cash / card / refund pre-auth)POST /folios/{id}/settleCash settles offline; card requires online vendor
4Check out (status flip)CheckOutActionPOST /reservations/{id}/check-outlocal outbox
5Auto-create housekeeping tasktoast notification (links to W-08)housekeeping-service.createTasklocal outboxCleaning-priority hint
6Revoke key credential(background)lock-integration-service.revokelocal revocation queued
7Optional review promptnotification-service outbound (T+24 h)Auto-translated review request (HITL approved per tenant)

Role variants

PersonaVariant
Front-DeskSettle, check-out within scope; refund > tenant-configured threshold requires GM
GMOverride on rate, refund, late-fee, dispute; reason code mandatory
Manager (chain)All single-property GM scope across multiple properties via tenant switcher

Offline fallback

Mostly offline. Cash settlement, status flips, folio adjustments, key revocation all write to the local outbox. Card settlement and lock-vendor cloud calls require connectivity (re-tried on reconnect; user is told explicitly).

Telemetry

frontend.desktop.modification_started · frontend.desktop.folio_charge_added { kind } · frontend.desktop.checkout_completed · frontend.desktop.key_revoked { reason } · frontend.desktop.dispute_overridden { reasonCode, actorRole }.


W-07 — Walk-in offline booking with cash deposit

Actors. Front-Desk Clerk · Walk-In Guest. Surfaces. Electron Desktop Backoffice (offline mode). Primary BFF. bff-backoffice-service (replays on reconnect). Services touched (eventual). reservation-service · inventory-service · billing-service · payment-gateway-service (cash intent) · lock-integration-service · notification-service. Realizes. J-07 · EP-MEL-06 + EP-MEL-10.

State diagram (offline-first)

Step-by-step (offline path)

#StepUILocal actionSync (on reconnect)
1Open Walk-In captureWalkInModule
2Verify availabilityAvailabilityPicker against local inventory_local tablelocal readAuthoritative re-check on reconnect; conflict if double-booked
3Pick room type, dates, occupancyRoomChoiceLocallocal computation
4Capture guest details (incl. document scan)GuestDetailsLocal (bilingual capture; document OCR via edge ONNX)local persistReplays as PATCH /draft
5Cash deposit (drawer counter capture)CashDepositCapture (denomination breakdown, change calc)local payment_intent (pending_cash) → capturedReplays as POST /payment-intents (method=cash, captured locally)
6Create reservation locallyWalkInConfirmationINSERT INTO reservations_local (status confirmed_local) + outbox entriesReplays as POST /reservations with idempotency-key
7Issue key (PIN / encoded card)KeyIssuanceWizardlocal PIN gen / encoder driverMobile-key (if applicable) issued on reconnect
8Print folio receipt + voucherdesktop print shelllocal PDF gen
9(Reconnect)SyncStatusPill: syncingOutbox FIFO drain; events emitted to Pub/Sub; conflicts → Sync Center (W-12)

Conflict policy on reconnect

  • Inventory race (room sold by online channel during offline window): per inventory-service policy — server-authoritative; local reservation flagged with inventory_conflict; clerk notified via Sync Center (W-12) to resolve (re-assign or refund deposit).
  • Reservation aggregate is append-only on the local side; server reconciles by clientMutationId.
  • Cash receipt is authoritative locally — never overridden by server. Cash drawer reconciliation (W-10) consumes the local truth.

Telemetry

frontend.desktop.walkin_started { offline: true } · frontend.desktop.walkin_confirmed_local { reservationLocalId } · frontend.desktop.outbox_drained { count, durationMs } · frontend.desktop.sync_conflict_surfaced { aggregate, count }.


W-08 — Housekeeping coordination + maintenance escalation

Actors. Housekeeping Lead · Housekeeper (kiosk sub-mode) · Maintenance Tech · Guest (mid-stay request). Surfaces. Electron Desktop Backoffice (Lead view + Housekeeper kiosk mode); Consumer Mobile Manage tab (mid-stay request). Primary BFF. bff-backoffice-service (operator) · bff-tenant-booking-service (guest mid-stay). Services touched. housekeeping-service · maintenance-service · reservation-service (touchpoints) · ai-orchestrator-service (task ordering) · notification-service · staff-service (assignments). Realizes. J-19, J-20, J-21, J-22 · EP-MEL-07 + EP-MEL-08.

Sub-workflows

SubTriggerSurfaceOffline
Auto-task on checkout (J-19)reservation.checked_out.v1Lead view: task auto-appears in queue with priority + AI-suggested orderLocal outbox; tasks replay on reconnect
Offline housekeeping update (J-20)Housekeeper kiosk picks up taskKiosk mode (large touch targets, single-handed operation)Full offline — all updates write to local SQLite; sync on reconnect
Mid-stay request (J-21)Guest request via Manage tabMobile → tenant BFF → housekeeping queueOnline-only (guest writes)
Cleaning reveals maintenance issue (J-22)Housekeeper escalationKiosk: "Raise maintenance ticket" → photo capture → ticket createdLocal outbox; photos compressed and queued

State (per task)

Step-by-step (housekeeper kiosk path, offline)

#StepUI (kiosk mode)Local actionSync
1Pick next taskHousekeeperKiosk (large tiles, AI-suggested order)local read
2Mark in-progresstile flips statelocal UPDATEreplays
3Encounter maintenance issue"Raise maintenance" CTAphoto capture (camera or upload) → local ticket draftreplays as POST /maintenance/tickets
4Mark donetile flips statelocal UPDATEreplays
5Lead spot-checkLead view "Inspect" actionlocal UPDATE (status=inspected)replays

Role variants

PersonaVariant
Housekeeping LeadFull queue management, assignment, AI-order accept/dismiss, sign-off shifts
HousekeeperKiosk sub-mode — large tiles, single-task focus, escalation path; cannot reassign
Maintenance TechMaintenance queue (separate module) — triage, parts/labor logging, close-out
GMRead-only across both queues; can override priority

AI affordances

  • Task ordering suggested per housekeeper based on room proximity, priority, ETA — <AISuggestionCard /> exposes "Why this order?" with the input features. Lead can accept (re-orders the queue) or dismiss (preserves manual order).
  • Photo quality scoring on maintenance ticket creation — flags blurry/dark photos and prompts retake before submission.

Offline fallback

Full offline. All housekeeping operations work offline; the kiosk is the most offline-tolerant surface in the platform. Mid-stay guest requests (consumer mobile) are online-only.

Telemetry

frontend.desktop.housekeeping_queue_opened · frontend.desktop.task_started { taskId } · frontend.desktop.task_completed { taskId, durationMs } · frontend.desktop.maintenance_escalated { taskId, ticketId } · frontend.desktop.ai_order_accepted / frontend.desktop.ai_order_dismissed.


W-09 — AI-assisted operations (pricing, dashboard, anomaly inbox)

Actors. GM · Front-Desk · Housekeeping Lead. Surfaces. Electron Desktop Backoffice. Primary BFF. bff-backoffice-service. Services touched. ai-orchestrator-service (cloud + local inference adapters) · pricing-service · reservation-service · housekeeping-service · maintenance-service · notification-service · audit-service. Realizes. J-15, J-16 · EP-MEL-11.

Surface composition

┌─ Daily Operations Dashboard (GM home) ────────────────────────────────┐
│ ┌───────────────────────┐ ┌────────────────────────────────────┐ │
│ │ Today KPIs │ │ AI Suggestions Inbox │ │
│ │ · ADR / RevPAR / occ │ │ · Pricing: +8% next weekend │ │
│ │ · arrivals / depart. │ │ · Housekeeping: deep-clean R204 │ │
│ │ · cash position │ │ · Maintenance: lock R301 battery │ │
│ │ · sync state │ │ · Anomaly: same card, 3 attempts │ │
│ └───────────────────────┘ └────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Activity Feed (live) │ │
│ │ · check-ins / check-outs / payments / key issuances / … │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘

Step-by-step (pricing suggestion accept, J-15)

#StepUIBFFProvenanceHITL
1Open AI inboxAISuggestionsInboxGET /ai/suggestions?inbox=open
2Review suggestion card<AISuggestionCard /> (pricing) — shows current rate vs proposed delta + horizon + confidencefooter: model · version · prompt · traceId · cloud
3Open "Why this?" drawer<AIExplanationDrawer />GET /ai/suggestions/{id}/explanation
4Acceptdialog confirms scope (which rate plan, which date range)POST /pricing/rate-plans/{id}/apply-suggestion (idempotency-keyed)Counter-signature recorded with reviewedBy + reviewedAt
5Audit + notificationtoast with "View audit" linkaudit-service writes; notification-service informs other GMs (multi-property)

Anomaly inbox

  • Same card used for ≥ 3 distinct payment attempts within 1 h → <AIAnomalyCard /> with payment trace; GM can flag the reservation, escalate to fraud review, or dismiss.
  • Cash drawer variance > tenant threshold → flagged; links to W-10 close.
  • Lock vendor heartbeat missing for room R301 ≥ 30 min → maintenance pre-ticket created; surface in inbox.

Daily Operations Dashboard (J-16)

  • Read-only KPI tiles (ADR, RevPAR, occupancy, cash position, arrivals/departures count, sync state).
  • Activity feed (live event projection from reservation.*, housekeeping.*, payment.*, lock.* events).
  • AI Insights inbox (unified feed of suggestions awaiting HITL).

Offline fallback

Partial. Dashboard KPIs derive from local SQLite (last-sync timestamp shown explicitly). AI inbox shows queued suggestions; new cloud-AI suggestions wait for reconnect; local-AI (edge ONNX) suggestions continue to fire (e.g., anomaly classifier on cash drawer).

Telemetry

frontend.desktop.ai_inbox_opened · frontend.desktop.ai_suggestion_viewed { id, kind } · frontend.desktop.ai_explanation_opened { id } · frontend.desktop.ai_suggestion_accepted { id, latency_ms } · frontend.desktop.ai_suggestion_dismissed { id, reasonCode }.


W-10 — End-of-day cash drawer close + regulatory submission

Actors. Front-Desk Clerk · Finance / Admin · Compliance Officer (signs regulatory exports). Surfaces. Electron Desktop Backoffice (print + export shells). Primary BFF. bff-backoffice-service. Services touched. billing-service (folios, drawer balance) · payment-gateway-service (card settlement reconciliation) · reservation-service (guest registry projection) · tax-service (where applicable) · audit-service · file-storage-service (signed PDF storage). Realizes. J-12, J-17, J-18 · EP-MEL-05 + EP-MEL-06.

Step-by-step (cash drawer close, J-12)

#StepUIBFFNotes
1Open cash drawer moduleCashDrawerCloseGET /cash-drawer/today
2Reconcile expected vs countedDrawerReconcileForm (denomination breakdown)POST /cash-drawer/{id}/reconcile { counted: { …denominations } }Variance highlighted ≥ tenant threshold
3Variance explanation (if any)reason code + free text(same call)Mandatory if variance > 0
4Sign-offclerk signature + supervisor signature (PIN step-up)POST /cash-drawer/{id}/closeBoth signatures recorded as audit fact
5Print reportprint shelllocal PDF genA4 + Letter; bilingual header
6Persist signed PDFupload to file-storage-servicePOST /file-storage/objectsSigned URL stored on close record

Step-by-step (regulatory submission — daily guest registration, J-18)

#StepUIBFFNotes
1Open regulatory moduleRegulatoryQueueGET /regulatory/pendingShows pending today + history
2Select submission targetper-regulator schema (AF MoIA, TJ MIA, etc.)GET /regulatory/schemas
3Preview payloadRegulatoryPreview (PDF + XLSX + CSV per schema)POST /regulatory/preview
4Compliance officer signsPIN step-upPOST /regulatory/submissionsRecorded as audit fact
5Dispatchregulator-specific channel (HTTPS API, SFTP, email)notification-service outbound where applicableConfirmation receipt persisted
6Show receipttoast + inbox entryReceipt PDF stored in file-storage-service

Step-by-step (monthly tax report, J-17)

Similar pattern: select period → preview → finance signs → export bundle (PDF + XLSX + CSV) → optional submission to tax authority adapter.

Offline fallback

  • Cash drawer close can run offline against local SQLite. Reconciliation, variance, and sign-off all persist locally; PDF stored locally; outbox replays metadata to server on reconnect.
  • Regulatory submission is online-only for actual dispatch; preview + sign-off can be prepared offline, but submission is held in outbox until connectivity returns.
  • Tax export can be prepared offline; submission online-only.

Telemetry

frontend.desktop.cash_drawer_close_started · frontend.desktop.cash_drawer_variance_recorded { amount, reasonCode } · frontend.desktop.cash_drawer_closed { signedBy } · frontend.desktop.regulatory_submission_signed { regulatorId, submissionId } · frontend.desktop.regulatory_submission_dispatched { regulatorId, latency_ms }.


W-11 — Tenant onboarding + theming preview/publish/rollback

Actors. Hotel Owner · Platform Admin · Marketing Reviewer (HITL on AI-drafted content). Surfaces. Control Plane (Phase 2) · Backoffice authoring console (today). Primary BFF. Control Plane BFF (Phase 2) / bff-backoffice-service (interim). Services touched. tenant-service · theme-config-service (drafts, preview, publish, rollback) · property-service (catalog) · iam-service (staff invites + roles) · notification-service (welcome email) · file-storage-service (logos, hero images). Realizes. J-13, J-14 · EP-MEL-01.

State (per ThemeVersion)

Step-by-step (publish path)

#StepUIBFFValidation gates
1Open theme draftThemeAuthoringConsoleGET /theme-versions/{id}
2Edit tokens / blocks / nav / flowlive preview iframe + side panelPATCH /theme-versions/{id} (debounced)inline contrast warnings via @ghasi/ui-melmastoon validators
3Generate preview link"Share preview" CTAPOST /theme-versions/{id}/previewReturns PreviewToken (TTL ≤ 7 days; noindex/nofollow)
4Stakeholder review (preview URL)tenant booking SPA in preview mode (read-only)GET /bootstrap?preview=…POST /quote returns MELMASTOON.BFF.TENANT.PREVIEW_MODE_READ_ONLY
5Publish"Publish version" CTAPOST /theme-versions/{id}/publishToken contrast, RTL parity, asset HEAD checks, AI-content HITL approval, booking-flow consistency
6CDN bust + Memorystore rotationtoast "Live now — first request after publish ~30 ms latency"(server-side workflow)
7Audit"View audit" link in toastaudit-service records publish

Rollback (O(1))

#StepUIBFF
1Open Versions tabThemeVersionsListGET /themes/{id}/versions
2Pick prior versionrow-action "Restore this version"POST /themes/{id}/rollback { versionId }
3Confirm (typed verb ROLLBACK)<ConfirmDestructiveDialog />(same call)
4CDN bust + audittoast "Rolled back" + "View audit"

Failure modes

FailureUI behaviour
Contrast invariant violationInline error per failing token pair; publish CTA disabled; "Open contrast checker" link
RTL preset missingBlock publish; surface preset id and missing variant
AI-drafted content awaiting HITLBlock publish; surface review queue link
Asset HEAD failure (logo URL 404)Inline error on the affected MediaRef; offer re-upload
OCC version mismatch (concurrent edit)"This draft was updated elsewhere — refresh"

Offline fallback

Online-only. Theme authoring requires authoritative server state, asset upload, and CDN bust.

Telemetry

frontend.theme.draft_opened { id } · frontend.theme.preview_issued { tokenId, ttlSeconds } · frontend.theme.publish_attempted { id } · frontend.theme.publish_failed { code } · frontend.theme.published { id, latency_ms } · frontend.theme.rolled_back { fromId, toId, reason }.


W-12 — Sync recovery & conflict resolution

Actors. Front-Desk Clerk · GM (override authority on conflicts) · Sync Engine (background). Surfaces. Electron Desktop Backoffice — Sync Center module. Primary BFF. bff-backoffice-service /sync/v1/pull + /sync/v1/push. Services touched. every domain service (writes drain through outbox; conflicts surfaced). Realizes. cross-cutting tail of W-07, W-08, W-10 (offline windows ending). · EP-MEL-10.

Sync Status Pill states (always visible top-right of desktop)

StateVisualBehaviour
onlinegreen dot + "Online"All flows work normally; outbox empty
syncingrotating dot (or static, reduced-motion) + "Syncing N pending"User can keep working; mutations append to outbox
offlineamber dot + "Offline since HH:MM"Online-only flows clearly disabled; offline-capable flows continue
pausedgrey dot + "Paused"User-initiated pause (rare; debug + bandwidth-budget)
conflictsred dot + "N conflicts — resolve"Click opens Sync Center directly to conflicts tab

Sync Center anatomy

┌─ Sync Center ─────────────────────────────────────────────────────────┐
│ Tabs: [ Overview ] [ Outbox (12) ] [ Conflicts (2) ] [ Audit ] │
│ ┌─ Overview ──────────────────────────────────────────────────────┐ │
│ │ Last successful sync: 14:32 (28 min ago) │ │
│ │ Pending mutations: 12 │ │
│ │ Bandwidth used today: 2.1 MiB │ │
│ │ Next attempt: in 0:45 │ │
│ │ [ Force sync now ] [ Pause ] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘

Conflict resolution UI (per aggregate policy)

AggregatePolicyUI
ReservationAppend-only locally; server merges on clientMutationIdSide-by-side diff; rare conflicts (only inventory races) → see below
InventoryServer-authoritativeConflict surfaces as "Room sold elsewhere" → reassign / refund deposit dialog
FolioLWW + diff (manual on conflict)Side-by-side Keep Mine / Keep Server / Merge per line item
KeyCredentialAppend-onlyNo conflicts; vendor adapter resolves
Theme draftOCC"Refresh and re-edit"
HousekeepingTaskLWW (last writer wins)Conflict only if Lead reassigns offline while another Lead reassigns online → dialog
MaintenanceTicketAppend-onlyNo conflicts
CashDrawerCloseLocal truth (frontend authoritative)Server reconciles to local; never overrides

Step-by-step (conflict resolution, inventory race after walk-in)

#StepUIAction
1Sync drains outbox; inventory race detectedtoast "1 conflict — open Sync Center" + pill flips to red
2Open Sync Center conflicts tabSyncConflictList
3Open conflict detailSyncConflictDiff (side-by-side: local reservation vs server-confirmed online booking)Shows guest names, dates, room
4Pick resolutionResolveConflictDialog — options: "Reassign to alternate room" | "Refund cash deposit" | "Escalate to GM"
5Apply resolution(per option)local outbox → server replay; audit fact written
6Notify guestnotification-service outbound (template per resolution)

Offline / online behaviour

The Sync Center itself is always available. Force sync requires connectivity (button disabled when offline; tooltip explains why). Conflict diffs are always viewable from local SQLite; resolution actions write to local outbox first, then replay.

Telemetry

frontend.desktop.sync_status_changed { from, to } · frontend.desktop.outbox_drain_started { count } · frontend.desktop.outbox_drain_completed { count, durationMs, conflicts } · frontend.desktop.sync_conflict_opened { aggregate, id } · frontend.desktop.sync_conflict_resolved { aggregate, id, resolution }.


3. Notifications and outbound messaging

Every workflow emits user-visible notifications through notification-service. The frontend never composes outbound text directly; tenant-customizable templates flow through EmailTheme + LocalePack.

TriggerDefault channelUrgencyTemplate
Booking confirmed (cash)SMS + WhatsApp + emailnormalbooking.confirmed.cash
Booking confirmed (card)SMS + emailnormalbooking.confirmed.card
Pre-arrival reminder (T-24h)SMS + pushnormalbooking.reminder.24h
Mobile-key deliveredpush + SMSnormalkey.delivered.mobile
Folio receiptemailnormalfolio.receipt
Refund issuedemail + SMSnormalpayment.refund
Mid-stay cleaning request acknowledgedpushnormalhousekeeping.request.ack
Regulatory submission accepted(operator-facing) inbox + emailnormalregulatory.accepted
Cash drawer variance > threshold(operator-facing) inbox + push to GMhighfinance.drawer.variance
AI suggestion awaiting review(operator-facing) inboxlowai.suggestion.queued
Sync conflict surfaced(operator-facing) inbox + pill alerthighsync.conflict.surfaced

PII never leaks into push / SMS payloads — templates are generic; detailed content accessed only inside an authenticated surface.


4. Error and exception patterns (frontend surface)

ClassPattern
Validation errorsInline, aria-describedby tie; surface stable error code in monospace badge for support escalation
ABAC denialsNeutral message + (where applicable) break-glass invitation with reason code
Licensing / tenant-suspendedInline with "Contact platform admin" affordance; never raw 403
Hold expiryNon-blocking countdown banner at T-60 s; HoldExpiredBanner at T-0 with re-quote CTA
FX-snapshot staleNon-blocking banner offers re-quote; previously held price honored until hold expires
Network lossSync pill (desktop) / connection banner (mobile + web) flips; in-flight mutations preserved; user informed non-blockingly
Card declinedIssuer code mapped to plain language; offer alternate method or cash-on-arrival; hold preserved
3DS abandonedBanner + retry from Payment screen
Vendor (lock) downWizard offers PIN fallback (W-05); banner explains the credential will be re-issued on reconnect
AI refusalClear, neutral; rephrase invitation; never presented as system error
Audit-write failureThe entire write is rolled back; user sees "Try again"
Sync conflictSurfaced via pill + Sync Center (W-12); never auto-resolved on aggregates without LWW policy
Tenant suspended mid-sessionGeneric "Temporarily unavailable" page; no theme leak

5. Cross-references

Every step in this document maps to a journey in docs/journeys/01-core-user-journeys.md and a story in docs/07-epics-and-user-stories.md. The mapping is intentional and bidirectional:

  • Frontend changes that affect a workflow must update both this document and the journey file.
  • Service changes that affect a workflow must update the service spec and (if the UI is materially different) this document.
  • New workflows require a new W-NN entry here, a corresponding J-NN (or extension to an existing journey), and an epic linkage. Twenty hard-cap on workflows; new workflow requires ADR.

Per-workflow contract details (BFF endpoints, error codes, idempotency keys, FX-snapshot behaviour) live in the BFF API_CONTRACTS.md files referenced at the top of each workflow.


6. Why this set of workflows

These twelve workflows cover every persona, every surface, and every operational mode the platform supports. Each has an E2E gate because regressions on any of them would be noticed first by users in production:

  • W-01 → W-04 are the revenue path — guest discovery to confirmed booking. Regression here is a direct loss of GMV.
  • W-05 → W-07 are the operational path — the property's daily operations. Regression here means a clerk cannot check a guest in, which is a business-stopping incident in our Phase-1 markets.
  • W-08 is the service-quality path — housekeeping and maintenance. Regression here cascades into guest dissatisfaction within hours.
  • W-09 is the AI value path — pricing optimization, anomaly detection, dashboard intelligence. Regression here is a slow erosion of the AI-first commitment.
  • W-10 is the financial integrity path — cash drawer close + regulatory submission. Regression here is a compliance and audit incident.
  • W-11 is the tenant velocity path — onboarding and theming. Regression here means tenants cannot launch or adjust their brand.
  • W-12 is the resilience path — sync recovery. Regression here breaks the offline-first promise and erodes operator trust in the platform.

Offline fallbacks follow desktop/06-desktop-app-specification.md §6 invariants. AI affordances follow 04-frontend-design-guidelines.md §10. Role-based UI variants reuse the same component tree — divergence is carried in Zustand role selectors, not in duplicate screens.


7. Open questions

  • Whether the chain operator persona (Phase 2) gets an additional workflow (W-13: portfolio dashboard + cross-property rate strategy roll-out) or composes from W-09 + W-11 with a tenant switcher.
  • Whether the TV-mode arrivals grid (kiosk display in lobby) gets a dedicated workflow or stays an UI-only variant of W-05.
  • Final shape of the mid-stay in-app messaging between guest and property (W-08 sub-workflow vs new W-NN); pending Phase 2 scope decision in 01-web-and-mobile-specification.md §13.4.
  • Whether regulatory submission should split into per-regulator workflows (AF MoIA, TJ MIA, IR police) once the schemas diverge significantly, or remain a single W-10 sub-workflow with per-regulator preview templates.
  • Whether the AI explainability drawer (W-09 step 3) gets a dedicated micro-workflow + telemetry surface, or stays inline.

8. References