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.
Front desk
Section titled “Front desk”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.
Verification
Section titled “Verification”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:
pnpm install --frozen-lockfilepnpm --filter @guestcraft/web exec playwright install chromiumpnpm --filter @guestcraft/web buildGUESTCRAFT_E2E_ADMIN_URL=postgresql://postgres@localhost:3941/postgres pnpm --filter @guestcraft/web test:e2eGUESTCRAFT_E2E_ADMIN_URL=postgresql://postgres@localhost:3941/postgres pnpm --filter @guestcraft/web test:e2e front-desk.spec.tsThe 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:
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:e2edocker rm -f gc-e2e-browser && docker network rm gc-e2e-browserThe 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.
Team and account settings
Section titled “Team and account settings”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.
Account security
Section titled “Account security”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.
Folios
Section titled “Folios”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 and quotes
Section titled “Rates and quotes”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.
Property performance reports
Section titled “Property performance reports”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 and publication
Section titled “Channels and publication”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.
Channex browser verification
Section titled “Channex browser verification”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.
Online payments browser verification
Section titled “Online payments browser verification”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).
Booking policies and no-shows
Section titled “Booking policies and no-shows”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.
Integrated permission catalog
Section titled “Integrated permission catalog”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.
Security: cookie-authenticated writes
Section titled “Security: cookie-authenticated writes”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.
Audit automation and cash drawers
Section titled “Audit automation and cash drawers”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.
Organizations and workspaces
Section titled “Organizations and workspaces”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.