Salta ai contenuti

GuestCraft web

Questi contenuti non sono ancora disponibili nella tua lingua.

The Next.js application hosts the HTTP composition from @guestcraft/server/http. Its request proxy allows eval only on loopback HTTP for Next.js development tooling; hosted responses use a nonce CSP without that exception. Identity shell checks happen on the server; operational records use the generated SDK and @guestcraft/react/query factories with the authenticated cookie session. Never import database or domain adapters into browser screens.

Select a property to open Today. Its arrivals, departures, overdue departures and in-house lists lead to reservation sheets. Planning selects free unit ranges to prefill a booking; the same range can create an out-of-order or out-of-service room block. Hatched blocks open an edit sheet. Rooms shows dated blocks and each room’s open maintenance tickets, with Report a fault. Reservations supports server search, date/status/source filters, ordering and cursors. Departed stays retain their historical room in Reservations, detail and Planning through effective departure; current assignments remain distinct. Cancelled stays stay excluded. The shell supplies the single New reservation action. Reservations uses a compact wrapping filter row and separate sorting control. Both screens retain their context in the URL. A reservation also has a full detail route. Room types and physical rooms are configured under Settings; Rooms supports single and bulk housekeeping. The workspace guest directory includes contact edits, reservation history, owning-property correction and duplicate review. Strong matches are ordered by their matching signals; contacts shared by more than five live guests appear as groups with complete paged membership and manual pair comparison. Dismissed pairs are filtered on the server, with a Show dismissed toggle and audited undo. The merge dialog labels the current survivor and absorbed identity, compares actual field values, owners, reservation counts and latest consent decisions, and warns about absorbed withdrawals before confirmation.

Survivors link their absorbed identities through merge provenance. Absorbed profiles name and link the final survivor, preserve read-only consent evidence and offer no mutation or booking creation callout. Consent history is newest first, with actor names, reader-zone timestamps and current decisions independent of history paging. Merged contacts remain operator-editable; newer OTA contact snapshots show their differences and let permitted staff apply selected fields through an ordinary versioned guest edit. Guest forms accept international email addresses, search localized country/language names and save canonical codes; existing language variants survive edits. See the guest workflow. Housekeeping icons, labels and compact letters come from the kit’s StatusBadge.

Creation supports several rooms in one booking, with a type, occupancy, rate plan and price for each room. Duplicate room adds a copy with the same type, dates, occupancy, rate plan and price but no physical room; Add room and Duplicate stop at the 20 rooms a booking accepts, with the reason shown. Availability and quotes are shown per room; the final preview validates the whole booking atomically. Detail and Planning display every booked room. Price preview freezes nothing: final submission checks availability again. Lifecycle actions and assignments use current versions. Planning adds edge resizing, keyboard move mode (M, arrows, Enter/Escape), protected-stay confirmation and ten-second version-checked Undo (Ctrl/⌘ Z in Planning). The date-change dialog is the non-drag alternative. Same-day check-in/out corrections offer Undo; cancellation requires a reason and explicit confirmation. Setup, stay and guest drafts remain in memory. Leaving a sheet discards its unsaved draft. Guarded outbound ARI delivery is implemented; see Channels and publication below. Housekeeping (…/housekeeping) is the day’s board by room, arrivals first: it generates or updates the day, counts its work, filters by status, member and type, and starts, finishes, skips, reopens and assigns tasks one by one or up to 100 at once, each with a 10-second Undo (the inverse change with the new version; a conflict is shown, never overwritten). Maintenance (…/maintenance) lists tickets with filters and opens a panel per ticket with status changes (closing asks for a clean or inspection follow-up), photos read through the API, editing and activity. Reporting a fault, from a task, a room or the maintenance page, creates the ticket and its photos with client identifiers, downscales photos to 1,600 px JPEGs in the browser, and can take the room out of order or service. …/settings/housekeeping holds the stayover cadence and inspection setting. See ADR 0046. Settings → Privacy (…/settings/privacy) holds the workspace’s guest data retention (the data-protection jurisdiction and a period or the default per purpose, with each default’s source; Messages, technical and authority periods read-only) and the history of erasures and exports. A guest profile loads its Personal data section only for members holding guests.legal_hold, guests.export or guests.erase: legal holds, the access export (downloaded once) and the erasure, whose confirmation lists what goes, what stays and what only Channex and the OTAs can erase. See ADR 0052. The mobile app is separate. Rate plans, nightly prices/restrictions and quoted bookings are available under Rates and availability and property settings (ADR 0027). See ADR 0026.

Form precision, preferences and validation

Section titled “Form precision, preferences and validation”

Booking and bulk-rate money inputs share ISO 4217 precision independently of CLDR formatting: HUF accepts two fractional digits, IQD/KWD three and JPY none, with locale-aware input. Values remain exact decimal strings on the wire.

Every request loads the English catalog, including requests with an old guestcraft.locale=it cookie, and the server picks an English regional formatting tag from the reader’s Accept-Language with englishFormattingLocale, such as en-IT for it-IT. The i18n README states the rule; ADR 0012 records how the browser checks its own data after mount, switching to en-001 when it lacks the region, and what was measured in Chromium, Firefox and WebKit.

Staff money uses the locale’s number separators for both display and input, through formatStaffMoney of @guestcraft/ui; Biome refuses a direct formatMoney import here. Guest documents keep CLDR’s currency separators. The current form offers no independent formatting-region or instant-display-time-zone selector.

Desk error-summary consumers pass a submission attempt counter, including server failures. Repeated invalid saves focus the summary; editing alone keeps field focus. Booking errors retain their body JSON pointers so guest notes, reservation notes and room fields link to their own controls. Unrepresented fields link to the form without pretending they belong to a same-named sibling.

pnpm quality runs component behavior and axe WCAG 2.2 AA assertions alongside the real PostgreSQL integration suites. Browser acceptance uses a production build, a disposable PostgreSQL database and a real session. No customer account or service is needed:

Terminal window
pnpm install --frozen-lockfile
pnpm --filter @guestcraft/web exec playwright install chromium
pnpm --filter @guestcraft/web build
GUESTCRAFT_E2E_ADMIN_URL=postgresql://postgres@localhost:3941/postgres pnpm --filter @guestcraft/web test:e2e
GUESTCRAFT_E2E_ADMIN_URL=postgresql://postgres@localhost:3941/postgres pnpm --filter @guestcraft/web test:e2e front-desk.spec.ts

The first command runs the default list of independently seeded specs; the second runs the first-run front-desk workflow, which needs an instance that does not exist yet and so has its own harness run.

The PostgreSQL URL must permit creating and dropping a disposable database. The harness creates its own database, instance secrets and administrator, starts the built application on port 3943, runs the browser workflow, then terminates its process group and drops only its own database. Override GUESTCRAFT_E2E_BASE_URL to use a different free local port, or GUESTCRAFT_E2E_CHROMIUM for an already installed Chromium executable. Never point this acceptance harness at a customer instance.

Chromium aborts the requests in flight with net::ERR_NETWORK_CHANGED whenever a network interface or address of its machine changes, which every Docker network or container started or removed does. On a machine shared with other Docker users, run it in a network namespace of its own, as the browser CI job does:

Terminal window
export GUESTCRAFT_E2E_BROWSER_WS="$(node apps/web/scripts/browser-server.ts gc-e2e-browser)"
GUESTCRAFT_E2E_ADMIN_URL=postgresql://postgres@localhost:3941/postgres pnpm --filter @guestcraft/web test:e2e
docker rm -f gc-e2e-browser && docker network rm gc-e2e-browser

The script starts a Playwright server in the official Playwright image, pinned by digest at the version of @playwright/test. It runs the installed playwright-core and is published on the loopback only. The container is alone on a bridge network of its own; both take the name given to the script. The tests then connect to it instead of launching Chromium, so the local playwright install chromium is not needed; the regional scenario still launches a local WebKit (playwright install webkit). That browser opens localhost and the other loopback addresses through the test runner. Base URLs, cookies and origin checks are therefore unchanged, and screenshots are still written on the runner’s side. Without the variable the tests launch a local Chromium.

The start waits at most 60 seconds for the server (a third argument changes this), and stops at once when the container exits. When the start fails or is interrupted, the script removes the container and the network that it created, and nothing else: an existing container or network with the same name is left alone and makes the start fail.

The server has no authentication. Its own network keeps other containers on Docker’s default bridge away from it, but it is not isolation from the other users of the machine: anyone who can open a loopback connection there can create browsers on it and, if they learn a client’s tunnel port, reach that client’s loopback through it. Those users can already reach the runner’s loopback services directly. A secret endpoint path would not help, because the server reveals it at /json. Do not run it where you do not trust the machine’s other users.

node apps/web/test/e2e/network-churn.ts <network prefix> <port> reproduces the failure. For two minutes it creates a Docker network about once a second and removes the previous one. Meanwhile Chromium loads pages with many slow resources from a server on the port. At the end the script removes its networks, prints the failed requests by error, and fails when there are any. A local Chromium loses thousands of requests with net::ERR_NETWORK_CHANGED; through the variable’s server none fail. Ctrl-C or SIGTERM stops it early: it still removes exactly the networks it created, closes the browser and the server, and exits with a failure status. The churn also disturbs every other local Chromium on the machine, so run it sparingly on a shared one.

Set GUESTCRAFT_E2E_SCREENSHOTS to a local output directory to also capture the front-desk feature screenshots. The 36-view theme/viewport matrix (English, light/dark, 1440×900 and 390×844, axe-audited) is a separate spec with its own synthetic tenant, property and 240-second deadline; see test/e2e/README.md. The Channex import audit matrix still shares its workflow deadline. A timeout does not establish an accessibility violation.

The workflow verifies setup → property → room type/room → guest/booking preview and save → Today/Planning → assignment → check-in/out → dirty/clean housekeeping → cancellation and availability recovery, with reloads, invalid fields, an independent competing booking (409), a competing editor (412), and a viewer denied in both UI and API. The viewer fixture uses real database memberships to verify isolation independently of the team administration screen. The browser CI job runs this workflow on each PR in its own harness run (test:e2e front-desk.spec.ts). Every harness run writes its results to reports/desk-e2e.json, so the uploaded artifact holds the last run’s. Traces are disabled so bootstrap/session material is not captured in trace archives.

Client-handled forms use the app-local PostForm so native submission before hydration or without JavaScript uses POST. React function actions supply their own POST method. Biome guards this app and production packages/ui/src sources: it rejects raw JSX forms, React Aria Form imports (including namespace imports), literal createElement("form", …) calls, and JSX or literal object properties named formMethod/formAction, including computed literal keys and shorthand properties, and assignments to members with those names (button.formMethod = …, button["formAction"] = …). Arbitrary dynamic construction is not analyzed. The app-local primitive and shared UI test fixtures are excluded from the form policy. Run pnpm --filter @guestcraft/web test:e2e:forms to verify native sign-in submission against its own disposable instance, independently of front-desk setup.

Settings provides Users and roles, Activity log, Role permissions, API keys, Webhooks, Events, Account and Appearance. Members with team.manage (built-in: workspace admins) manage local members; organization owners/admins additionally select organization membership. Invitation links are available to copy. They are accepted at /invitations#token. New accounts register there; existing users sign in on that page before acceptance. Role/removal changes affect the next request. Account controls update name/password, revoke other sessions and list and revoke the mobile app’s devices. Pair a phone shows a one-time code as a QR code, follows it until the app uses it or it expires, and withdraws an unused one on close or replacement; a withdrawal the instance does not confirm stays visible, with its countdown and a retry, until it succeeds or the code expires, and leaving the page sends it best effort (keepalive), which nothing can confirm offline; its address, /mobile/pair, tells a phone’s browser to scan it with the app (ADR 0041, “QR pairing”). An organization owner transfers the ownership to another organization administrator from Users, after typing the organization’s name. API keys can be rotated with a grace period and limited to client addresses; Webhooks replays and redelivers events; Events lists the catalog’s events of the last 30 days (events.read). Activity log, and organization Activity, page with keyset cursors and export the filtered entries of a from–until range of at most 366 days as CSV or JSON (at most 10,000 entries; each export is audited). See ADR 0030 for acceptance evidence and limits. The browser CI job runs this team workflow, with its own seeded owner, against the production build and disposable PostgreSQL.

Sign-in asks an account with an authenticator app for its code, or a recovery code, on the same page, and can remember the browser for 30 days; Sign in with a passkey needs no email. Account settings load a Sign-in security section: authenticator setup (QR code, setup key and ten recovery codes shown once, to copy or download), new recovery codes, turning it off, and passkeys (add, rename, remove). A change the instance refuses until this session proves a factor opens a verification dialog, then retries. A workspace whose organization asks this session for a stronger factor sends it to /security-check?workspace=…; the dashboard names a grace period’s deadline. Owners and administrators manage /organizations/{id}/security: the sign-in policy (If-Match), members’ factors and standing, one-time password reset links, and the API keys and organization tokens of members who fall short. /password-reset#token redeems a link. Setup keys, recovery codes and reset links stay in component memory, are shown once and never enter a URL query, a log or a screenshot (ADR 0053). Passkey code (@simplewebauthn/browser) loads only when a ceremony starts; browsers without WebAuthn or a secure context get an explanation instead. pnpm --filter @guestcraft/web test:e2e security.spec.ts walks the workflow, with a CDP virtual authenticator for passkeys.

Reservation folios and manual settlement are documented in the folio workflow and ADR 0029. The ledger, frozen room charges, property extras, manual payments/refunds, a check-out that settles only at the last room and never holds a credit (an owed balance needs settlement or an audited administrator approval), refunds due, net and full-ledger views and a non-fiscal statement are implemented. Taxes need a country module on the TaxCalculator port (with a re-rate operation); fiscal documents and online providers remain planned.

Rates supports range/weekday edits, inline pricing, fill, TSV clipboard and revision-checked Undo. A per-person plan expands into read-only prices per adult count. Settings → Rate plans edits where the plan is sold (direct only, or through Channex on the ticked channels, each saying what it can represent), the sale basis, the occupancy curve with a price preview, the infant and child age bands and the included package (loaded only when the editor opens); a setting the plan’s channels cannot represent is refused on its field. New reservation and room repricing use nonholding quotes with each child’s age (a booking or room edit sends back the quote’s fingerprint); overrides are role-checked and remain visible in detail, with the children’s ages and the included package. A channel booking whose price could not be split into room and package says so on its room. The folio lists package services apart from the accommodation. See the rates workflow.

The property Report screen supports 1–366 stay nights grouped by day, Monday-based week or month, with URL-persisted filters, KPIs, occupancy/ADR chart, equivalent accessible table (including whole-range totals), source breakdown, CSV and visible definitions in English. Admin, revenue_manager and accounting see the entry; other roles receive a permission state and API 403. Performance uses historical room, room-type and out-of-order capacity; periods before capture use labelled baseline inventory and show the number of affected nights. Pickup compares captured reservation books at two end-of-day observation dates. Cash reports payments and refunds by recorded service date and method; folio revenue uses each charge’s service date, allocating room charges to their stay nights and including module levies. Each report provides a daily chart, equivalent table and CSV. Cancellation-event trends remain planned. Browser acceptance also exercises report persistence, keyboard chart navigation, CSV, mobile layout and viewer denial.

Channels configures Channex mapping and webhook registration, and displays import health/quarantine/ack state. OTA stays appear throughout the front desk with channel metadata and protected commercial fields. See the booking-import contract for ownership and the ARI workflow for separate activation and recovery.

Guarded ARI publication has rate-plan mapping, numbered property leases, booking barriers, shared pacing and health/resync with explicit takeover. Automatic catch-up and bounded unknown repair are implemented; the health panel shows the nightly comparison with Channex (last check, nights, differences, repairs still sent or rejected, and why a check waited or failed) and, per send, how often Channex was asked about an unknown outcome and what it answered. See the ARI workflow.

Today, Planning and Reservations list the property’s open Channex alerts (sync failure, overbooking, unrepaired drift, quarantine) to members with channex.view_health, each with a link to its fix: publication health or the revision queue in Channels (?property= selects the property and the fragment focuses the section), or Reservations filtered on overbookings. Channels shows the same alerts for the selected property, and the latest import of its existing Channex bookings; members with channex.configure preview that import in a dialog (counts, first conflicts, incomplete reads) and queue it. A reservation the channel reinstated says so on its detail.

All scripts below use test/e2e/run-channex.ts, the production-built app, the HTTP simulator and disposable PostgreSQL 17. The runner serves Channex production (GUESTCRAFT_E2E_CHANNEX_PORT, default 3982) and a separate, empty staging installation (GUESTCRAFT_E2E_CHANNEX_STAGING_PORT, default 3981); the app’s calls to https://app.channex.io/api/v1 and https://staging.channex.io/api/v1 reach them, and any other channex.io address fails without touching the network. Use the build and browser setup under Verification, set GUESTCRAFT_E2E_ADMIN_URL to an administrator URL that permits creating/dropping databases, and run pnpm --filter @guestcraft/web <script>:

Script Scenario
test:e2e:channex Booking import, mapping and unlink/recovery
test:e2e:ari ARI publication and OTA closure with the resident worker
test:e2e:ari-pump The ARI scenario with Vercel production configuration and no resident worker; only request after() pumps advance publication and webhook intake
test:e2e:messages Messages delivery with Vercel request pumps and no resident worker; English light/dark desktop/mobile captures
test:e2e:origin Cookie-write origin checks, including a credentialed request from a second localhost origin
test:e2e:channex-certification A sandbox workspace’s Channels screen names Channex staging, shows staging’s refusal of a production key by the field and keeps a staging key after reload; certification scenario 10 books five twin rooms (Duplicate room) and a later double in one reservation on New reservation and checks the availability Channex then holds, with one availability call. Axe and light/dark captures under reports/screens/channex-certification. Seeds a twin (8) and double (2) room type (GUESTCRAFT_E2E_MULTI_ROOM=1) and runs the resident worker

The harness creates and drops its own database and stops its app, worker (when used) and simulator. It redirects Channex production and staging HTTP calls to the local simulators; tests post webhook deliveries themselves. No customer account is needed. Never point the harness at a customer instance.

GUESTCRAFT_E2E_BASE_URL selects the app address (default http://localhost:3983); GUESTCRAFT_E2E_CHANNEX_PORT selects the simulator port (default 3982) and GUESTCRAFT_E2E_CHANNEX_STAGING_PORT the staging one (default 3981). GUESTCRAFT_E2E_CHANNEX_URL and GUESTCRAFT_E2E_CHANNEX_STAGING_URL can override the simulator URLs, which must include a port. Use assigned isolated ports, normally within 3980–3989. The origin scenario also uses the app port plus one; reserve it too.

pnpm --filter @guestcraft/web test:e2e:payments (test/e2e/run-payments.ts) runs the online payments workflow against the production build, disposable PostgreSQL 17 and the Stripe simulator, with Vercel request pumps and no resident worker: connecting Stripe, a balance link paid by a guest on the hosted page, a partial refund, a card-guarantee link, Payments due and a member without payments.configure. It writes English light/dark desktop captures to reports/screens/payments. The app process calls the simulator instead of api.stripe.com; the hosted pages are served over HTTPS on localhost with a certificate openssl makes for the run, because the link page lets its form continue only to an HTTPS page. GUESTCRAFT_E2E_STRIPE_PORT selects the simulator port (default 3984) and GUESTCRAFT_E2E_STRIPE_HOSTED_PORT the hosted pages’ port (a free one by default).

The property policy editor, frozen quote/booking terms, simulated cancellation with an audited fee override, explicit no-show and same-day undo, guarantee attestations and prepayment tracking are available in English. See the policy workflow and ADR 0032 for permissions, deadlines, migration semantics, SDK operations and planned OTA reporting/provider capabilities.

Workspace onboarding offers property creation only with unrestricted properties.configure access. Rate-plan settings require rates.read, inventory.read, properties.read and policies.read; missing rates, inventory or policy reads show a permission notice before the screen requests those resources.

Module action controls consume live catalog grants, including separate folio payment and re-rate actions, policy overrides, OOO/OOS management and maintenance. The existing role browser scenario verifies persisted custom access. See roles.

Messages is available in workspace/property inboxes through catalog permissions. The inbox supports shared read state, templates, plain-text replies, booking and inquiry attachments, and confirmed Airbnb decisions. Inquiries offer pre-approval, whole-unit special offers in the booking currency, or no-call lapse; reservation declines expose a reason and optional guest/channel messages. Workspace retention settings support clearable purpose overrides. Guest profiles and settings expose typed local erasure to unrestricted members with messages.erase; API keys cannot erase. Inquiry attachment delivery still needs live Channex certification. Inquiry-only content without a trusted GuestCraft guest link and upstream/archive/audit copies remain outside guest erasure.

All unsafe API methods (POST, PUT, PATCH, DELETE), bodyless included, require an exact configured trusted Origin. If Origin is absent, only Sec-Fetch-Site: same-origin or none is accepted. Missing evidence, same-site alone, opaque and untrusted origins receive the request.untrusted_origin problem (403). The sign-in form explains an unconfigured address. Non-browser cookie clients must send the instance Origin on writes. Trust comes from instance configuration, never request Host/Forwarded headers. See ADR 0020.

Webhook signal and cron routes authenticate their own secrets outside the cookie API; setup uses a private code and Next.js server-action origin checks. The Channex browser verification scripts include the origin regression.

Channels defaults to shared Channex accounts. Members with channex.configure and channex.manage_account_mode link a Channex property ID before room discovery, then map rooms and register its property webhook. The account-mode panel explains exclusive consumption, confirms the change, and persists it through the conditional connection API in English.

Shared-account property linking looks up a candidate title/ID and requires an explicit acknowledgement warning before saving an inactive link. Mapping a room type activates intake. Audited unlink has an English confirmation, retains imported reservations, and offers retry if upstream webhook removal fails. Browser acceptance captures both dialogs and verifies persistence/relink against the simulator.

Night audit is a property screen for members with night_audits.run: resolve blocking stays, acknowledge warnings, close one business day and reopen its durable result after reload. Today displays the persisted business date; reports default to its month. Administrators configure local-time automation and automatic no-show decisions; expired tentative options release fee-free and Cash drawer supports explicit counted shift closure. See the contract. Run node test/e2e/run.ts night-audit.spec.ts with the built application and disposable database.

Night audit settings, configured no-shows and expired tentative options share the durable property-date audit workflow. Cash drawer shifts provide currency-aware opening float, server-linked cash activity and versioned counted closure. See the night audit contract and cash shift contract for public operations, permissions, recovery, persistence and the Vercel daily-trigger cadence limit.

The shell’s Organizations and workspaces link opens the directory at /organizations. Organization owners and administrators create empty production or sandbox workspaces; instance administrators create organizations with their first workspace and become their owner. Members with unrestricted workspace configuration access can open name settings. Settings keep invalid or conflicting drafts, refresh the saved version without discarding them after a failed save, and require explicit archive and unarchive confirmation. Archived data remains stored, while operational access is suspended; unlink Channex properties and finish webhook cleanup before archiving. Saved credentials remain available after restoration. Organization administrators retain the settings needed to restore access.

Organization Activity at /organizations/:organizationId/activity shares the workspace Activity filters; organization paging uses opaque keyset cursors. The workspace switcher shows the workspace name with its organization as secondary option text, distinguishes sandbox and archived workspaces, and remembers the last five reachable choices per user on that browser. Active workspaces are preferred on landing. Archived entries appear only to members who may configure or restore them. A single workspace has no redundant selector. Settings currently store the name and the immutable creation environment; no workspace locale or measurement defaults exist.

pnpm --filter @guestcraft/web test:e2e organizations.spec.ts verifies creation, persisted name editing, archive recovery, organization Activity, recent switching and denied access against the built app and disposable PostgreSQL. It captures English light/dark desktop and mobile settings states and runs axe in both themes and high contrast. See the tenancy contract for API and scope. The default harness also runs tenancy-review.spec.ts for keyboard selection, an open-list axe audit, the sandbox Channex staging notice and shared-browser user-specific recents. Landing, workspace-recents, and per-workspace property-recents cookies use an opaque SHA-256 actor hash; legacy browser-wide names and raw usr_… identifiers are ignored.

Sandbox marks separate training data and gc_test_ API keys. A sandbox workspace connects to Channex staging (staging.channex.io) and needs a key from a Channex staging account; a production workspace connects to Channex production (app.channex.io). The connection panel names the environment the key is sent to before key entry, and a refused key’s error names the environment that refused it without claiming why. A sandbox workspace whose key was saved before sandboxes used staging stays on Channex production, with a warning, until its properties are unlinked, their webhook removal is finished and a staging key replaces it.

Restore confirmation and the success notice ask operators to review property business dates and explicitly re-enable paused night-audit automation. Pending webhook deliveries strictly older than 246,960 seconds (68h36m) expire on restore and remain in the delivery log. Stale queued events that have not reached delivery are not dispatched automatically either. Their domain events are retained for manual replay within 30 days of the event. See the tenancy guide.