0026. Front-desk screens
Questi contenuti non sono ancora disponibili nella tua lingua.
- Status: Accepted
- Date: 2026-10-27
Observable acceptance criteria
Section titled “Observable acceptance criteria”An administrator configures room types and physical rooms and sees them after reload. A receptionist searches or creates a guest, previews an exact price and books a stay, finds it in Today, Reservations and Planning, assigns a free room, checks in and checks out. Checkout marks the room dirty; housekeeping can mark it clean or inspected, singly or in bulk. Cancelling an expected reservation releases capacity. Same-day lifecycle corrections offer Undo. A viewer can inspect without writing. Invalid fields, capacity conflicts and stale versions leave drafts intact and offer a clear recovery path. Every route reloads its persisted data and retains URL filters; both English and Italian and keyboard operation are supported. Production browser tests use real PostgreSQL. The explicit implementation brief substitutes for a separate feature issue.
Screen map
Section titled “Screen map”Property routes /w/:workspace/p/:property/{today,planning,reservations,rooms,settings/rooms}
share the shell and a property-scoped query cache. /reservations/:id reloads detail;
?reservation=:id opens the same detail above Today, Planning or the list. ?new=1
opens creation, with optional arrival, departure, room_type_id and unit_id prefill.
Workspace /w/:workspace/guests lists and edits guests and their reservation history.
Property selection and the home redirect land on Today.
Data and mutations
Section titled “Data and mutations”Use the generated SDK transport and React Query factories. Cache lifetime is bounded to one workspace/property. Abort old reads when leaving context, clear caches on unmount, and invalidate related reads after confirmed mutations. Server versions supply If-Match; no automatic mutation retries. Readiness, capacity and availability stay authoritative on the server. Price preview uses validate_only and never holds a room. Saved pricing uses decimal strings and currency precision; stay dates use Temporal half-open intervals.
Lifecycle actions show immediate pending feedback, then authoritative results and a 10-second keyboard-accessible Undo toast for supported same-day corrections. We avoid optimistically representing a successful check-in or inventory hold before the server accepts it: concurrent assignments and readiness can reject it. 409 shows the holding reservation or full nights, 412 offers explicit reload before editing again. Guest contacts and writable drafts stay in memory, outside browser persistent storage and query keys. Search terms enter short-lived query keys and list URLs as requested by the screen contract.
Planned
Section titled “Planned”Rates and availability editing, Channex synchronization/import/mapping, folios and payments, reports and the mobile app were planned; ADRs 0027 to 0029, 0031, 0034 and 0041 have since implemented them. No channel connection is treated as synchronization. Manual screen-reader certification remains separate from axe.
Verification
Section titled “Verification”Acceptance evidence and implementation decisions are appended after the gates run.
Implementation decisions for coordinator review
Section titled “Implementation decisions for coordinator review”- The assigned brief takes precedence over the older common brief’s base SHA: this branch starts at 297be8a, which contains the shell, planning kit and reservation contracts.
- Creation sells a single room; every room of a multi-room reservation remains visible and actionable in detail. Upgrades/mid-stay moves remain refused by the API.
- Successful creation opens its persisted full detail immediately. List/planning entry points use URL sheets; a copied detail URL is still usable after reload.
- Dates use the shared React Aria picker and explicit ISO/Temporal conversions, so app language controls date segments. Full detail uses two columns on larger screens and one on narrow screens. Reservation filters wrap compactly; sorting sits beside the table.
- The directory and lifecycle forms keep sensitive drafts in memory. Closing a sheet discards an unsaved draft; persistent cross-device drafts require a future server model.
- Room-range creation is bounded to 100 sequential independent writes. It reports failed room names and retains successful rooms; it does not claim an atomic bulk-create API.
- A separate browser CI job is justified: it catches integration gaps in roughly 15 seconds of browser execution after building. It uses PostgreSQL 17 and generated disposable secrets, with no customer credentials. Viewer membership is a database fixture because user/member administration is not an implemented operator workflow.
- React Aria’s virtual-focus live announcer retains references for seven seconds after overlay options unmount. Component audits include the live region after this bounded announcement lifetime rather than disabling rules or removing nodes. Production audits cover settled screens; manual assistive-technology review remains a release task.
The final main integration includes shell polish (PR #24) and codegen isolation fixes (PR #25), through 9218a3a. Guest/reservation search now uses the extensible CommandResult list with distinct icons and localized type labels. The separate data hook retains its request cancellation and tenant-scoped lifetime. Property commands continue to open Today. The merged shell backdrop, bounded selectors and narrow planning help remain intact.
Revision 1.4.1 adds the historical physical room projection to CalendarStay. Planning places departed stays on stayed_unit_id through effective_departure, in the existing muted style; unit_id still means a current assignment. The default calendar continues to exclude cancelled stays and archived setup. No historical occupancy is invented for zero-night stays. Undo errors are owned by the property scope, so closing a detail sheet cannot hide a failed correction; notifications and cache clear together when the property/locale changes.
Final acceptance evidence (initial screen round)
Section titled “Final acceptance evidence (initial screen round)”The final pnpm quality run passes: 2,726 tests passed, with eight existing optional
upstream drift tests not run. Global coverage is 97.16% statements, 93.27% branches,
96.15% functions and 97.99% lines; web function coverage is 87.67%, above its unchanged
85% threshold. No handwritten source was excluded and no threshold was lowered.
Earlier full runs hit five-second UI test deadlines under contention, including the
planning accessibility audit. The final run caps Vitest at two workers with
VITEST_MAX_WORKERS=2, matching available resources on the shared host. All original
fixtures, deadlines, axe rules and coverage thresholds remain unchanged; no retries or
source exclusions mask the failures. A separate pre-existing worker integration race was
also exposed: handler observations became visible before pg-boss committed completion.
The test now waits for both durable completed rows before asserting their states; its
tenant isolation assertions and timeout remain unchanged. This test-only correction is
included here so the complete gate checks the final branch.
The web production build, Docker image build, REUSE and commitlint pass. The built
standalone Docker image additionally passes health, fresh-instance setup, API reference
and OpenAPI smoke checks against a separately migrated real PostgreSQL database.
The production Playwright workflow passes with no retries: setup, room configuration,
price preview/save, reload, Today/Planning, assignment, check-in/out, dirty/clean rooms,
cancellation/capacity recovery, competing booking (409), stale editor (412), keyboard
selection from Planning, palette guest search, guest edit/reload and denied viewer writes.
The local Compose project is gc-desk, using ports 3940–3944 only.
A script retained in /tmp/guestcraft-desk/seed.mjs seeds synthetic data through the SDK:
three room types, twelve rooms and twelve reservations. Seventy-two screenshots and
Chromium axe audits cover all eight screens plus the reservation detail sheet, in English
and Italian: 1440×900 light/dark, 1024×768 light and 390×844 light. They record zero axe
violations, page errors or viewport overflow. Evidence stays outside version control in
/tmp/guestcraft-desk/screens and screenshot-audits.json beside it.
Design review corrected mobile tab overflow, collapsed advanced reservation filters, removed duplicate active navigation, replaced native date presentation with the locale picker, placed full detail in two desktop columns, kept dates on one table line, and separated the phone context selectors from the primary action. A final phone review also separates Today’s date toolbar from its heading and gives guest contact fields full width. These audits cover settled screens and automated keyboard behavior, not certification by human screen-reader users or a production INP performance benchmark.
The initial screen round kept the HTTP contract at revision 1.4.0; its committed
OpenAPI/SDK artifacts were unchanged and their conformance tests passed.
ToastOptions.action: { label, onPress } was that round’s only new exported UI contract;
booking creation uses it for the localized Open action.
Coordinator review fixes (C2)
Section titled “Coordinator review fixes (C2)”Acceptance: after regular or early checkout, reload Planning and see the departed stay on its occupied room through effective departure, with later nights free. Cancel/rebook must never put a cancelled stay on that room. Current assignments remain separately identifiable in Calendar/FrontDesk, OpenAPI and the SDK. Existing workspace and lifecycle grants apply.
Planning, Rooms and Today use StatusBadge’s housekeeping icons and labels: spray for dirty, sparkles for clean and shield-check for inspected. Compact badges use D/C/I in English and S/P/I in Italian, explained in the planning legend. Unassigned tracks have one visible per-type stay count and distinct accessible track names. The shell owns New reservation; pages retain contextual date controls. Today shows its locale-formatted date once. Reservations keeps searchable/status/source/date filters on a wrapping row, a ghost reset beside them and compact server sorting above the table, with URL persistence unchanged.
Guest creation/editing use the kit ComboBox with Intl.DisplayNames country/language names. Country choices are the same 250 canonical ISO/XK codes accepted by the domain, maintained as browser data to respect the no-domain-import boundary. Language choices are the catalog languages; existing regional or other valid saved tags remain selectable and unchanged. Clearing either field saves null. Server country/language validation remains authoritative. The price section and the parallel rates/restrictions work are outside C2’s changes.
The built browser regression seeds a previously checked-in stay on its departure day through the disposable database, then checks out via the API. This models an overnight stay without changing the real application clock. Early checkout is exercised through real API check-in/out; both histories are checked on their room after a planning reload.
C2 acceptance evidence
Section titled “C2 acceptance evidence”pnpm quality passes with 2,747 tests passed and eight existing optional upstream drift
tests not run. Global coverage is 97.16% statements, 93.32% branches, 96.17% functions and
98% lines, with all package thresholds enforced. The run uses VITEST_MAX_WORKERS=1 on
the shared external PostgreSQL cluster: two workers exposed a cluster-wide role-bootstrap
race (tuple concurrently updated) between integration files. Individual tests still run
their concurrent booking/transaction scenarios. No timeout, threshold, retry or exclusion
was changed to pass the gate. Earlier contention also exposed repeated country-choice
construction; the guest form now memoizes choices across unrelated field edits.
The web production build, Docker web/migrator builds and fresh-instance migration/health/ setup/API reference/OpenAPI smoke checks pass. Committed OpenAPI and SDK conformance, REUSE and Conventional Commits checks pass. API revision 1.4.1 adds nullable stayed_unit_id/name to CalendarStay (Calendar and FrontDesk); StatusBadge’s optional compact prop is the UI contract addition. PlanningGrid’s public props and the price section are unchanged.
The production Playwright workflow covers country/language selection, save and reload;
regular and early checkout retaining their room after a planning reload; and the original
conflict, stale-write, undo, housekeeping, cancellation and denied-viewer paths. Synthetic
before/after evidence is in /tmp/guestcraft-deskfix/screens/{before,after}: 72 views per
phase plus a viewer screenshot, across IT/EN, 1440×900 and 390×844, light and dark. All 72
after states receive axe WCAG 2.2 AA audits with zero violations. Close-ups include guest
fields and the housekeeping legend. Review corrected date-control overlap, narrow-track
count clipping, focused fields behind the sticky footer and inconsistent Italian inspected
wording. Manual screen-reader certification and a production INP benchmark remain separate.
C2 second review: summary history and ComboBox scrolling
Section titled “C2 second review: summary history and ComboBox scrolling”Acceptance: a regular or early departure retains its occupied room after reloading both
Reservations and detail. Summary rooms expose nullable stayed_unit_id/stayed_unit_name
under the existing 1.4.1 revision, matching CalendarStay. Active assignments remain separate;
cancelled stays and departures without an occupied night have no historical occupancy.
Property lists and guest history share this projection. No database migration is required.
The complete failed CI log contains one option detachment followed by a closed country popup, rather than evidence of a continuous remount loop. Production Chromium probes at 6× CPU slowdown found stable idle option nodes and no idle render loop, including repeated scroll/search/select cycles. The kit nevertheless had two different scrolling elements: React Aria uses its listbox ref for positioning, focus scrolling and restoration, while CSS scrolled the outer popover. The listbox now owns scrolling and the popover clips it. This applies to every kit ComboBox, including country/language and shell/settings selectors. The booking room/guest pickers use Select; date fields use DatePicker and have no analogous listbox-scroll mismatch. Their keyboard paths remain in the built workflow.
Regressions retain option DOM identity across unrelated parent renders, bound idle React Profiler commits, and use a production Chromium worker with 6× throttling for immediate country selection, idle node retention and scrolling the full country list to its end. The ordinary click occurs before any idle measurement. No force clicks, retries, increased workflow timeout, dependency change or coverage exclusion is introduced.
The scroll regression was checked against both implementations of the production bundle:
it fails before the fix (the focused option moves to the end but listbox.scrollTop
remains zero) and passes with the listbox as the scrolling element. This is independent
of the idle-node/Profiler checks; those did not reproduce an idle remount loop before the
fix either. The original CI detachment is therefore not described as a proven render loop.
Web review fixes: precision, regional preferences and error focus
Section titled “Web review fixes: precision, regional preferences and error focus”Acceptance for this focused correction is observable at existing form boundaries:
booking previews and bulk-rate saves preserve HUF two-digit and IQD/KWD three-digit
fractions in IT/EN; JPY refuses fractions. Both inputs use rateDecimals, the existing
ISO-aware helper, rather than CLDR display metadata. No public wire contract changes.
Catalog selection uses the supported language cookie separately from compatible
regional browser preferences for formatting. Saving theme/density must preserve date
segment order and number conventions (including en-GB, en-AU and it-CH). The form
has no explicit formatting selector, so it stores no new formatting cookie. An
independent formatting/time-zone selector remains product work under ADR 0012.
The booking, guest editor, room type/unit setup, policy, rate-plan, block and bulk-rate forms identify each submission attempt for ErrorSummary. Client failures and repeated server validation responses focus the summary; field edits do not change the attempt. Booking response mappings retain full body JSON pointers and distinguish guest notes, reservation notes and the first represented room. Other nested rooms or unknown fields link to the form instead of an unrelated same-named control. Permission and synchronization-state review findings remain separate work.