0023. PMS interface foundations
- Status: Accepted
- Date: 2026-10-27
Observable acceptance criteria
Section titled “Observable acceptance criteria”An authenticated operator can navigate Channels and Settings, search permitted workspaces and properties, and reopen a copied contextual URL. Administrators can create a property, edit its name, time zone and currency, and see the saved values after reload. Other roles can read properties with an explicit explanation of their disabled editing controls. The existing protected setup, sign-in, language change, sign-out and Channex connection workflows retain validation, recovery and protection against stale workspace responses. A new workspace shows a checklist derived from local properties and saved connection data. Keyboard users can reach the content, switch context, use menus and the command palette, close overlays and recover focus. The shell adapts to 390 px and remembers appearance, density and sidebar preferences. No navigation entry claims an unimplemented PMS screen.
Decision
Section titled “Decision”Adopt the shadcn React Aria Mira base: compact spacing, neutral surfaces, small radii and restrained emphasis suit repeated front-desk work. Adapt its source to GuestCraft’s 14 px text, logical properties, real focus outlines and minimum targets; do not copy Mira’s 10–12 px text or opacity-based disabled styles. Components remain owned source.
The official July 2026 Aria announcement and the aria-mira registry were inspected.
pnpm dlx shadcn@4.21.0 init --help and
pnpm dlx shadcn@4.22.1 init --base aria --help both fail while installing the CLI:
High-risk trust downgrade for "semver@6.3.1" (possible package takeover) through
@babel/core@7.29.7. Keep the supply-chain policy. Use the brief’s manual-copy fallback:
fetch the official registry sources, preserve shadcn’s MIT notice and adapt the Aria
composition to the existing package rather than install the CLI in production.
Tokens remain typed data with DTCG export. shadcn variable aliases refer to existing semantic roles, so all four themes share the existing contrast inventory. Inter is self-hosted through Fontsource (OFL-1.1), with no font network request at runtime or build. Density changes spacing and row heights (28/36/44 px), never type size. MoneyInput stays on exact decimal strings; React Aria NumberField is for counts only. Keep the existing React Aria toast adapter and its minimum timeout, F6 and polite announcements.
Scope and planned work
Section titled “Scope and planned work”This round provides reusable primitives and rebuilds existing setup, identity, property and connection screens. Reservations, guests, room management, rates, operational Today, planning, import, mapping, reservation intake and synchronization were planned UI work at this round; ADRs 0024 to 0028 and 0034 have since implemented them. A successful connection means credential verification and remote property listing only.
App shell and URLs
Section titled “App shell and URLs”The left navigation is 240 px or 56 px collapsed, with its state in a preference cookie.
Below 1024 px a modal Sheet provides navigation and returns focus on dismissal.
The top bar keeps organization, searchable workspace/property controls, help and account
controls in consistent positions. The shell accepts a primaryAction slot for the next
round. A typed navigation config contains only Channels, Properties and Appearance;
future screens add entries when their workflows exist.
Workspace pages use /w/:workspaceId/{properties,channels,appearance}. Property
settings use /w/:workspaceId/p/:propertyId/settings; a property-scoped channels URL
also exists, while the connection itself remains workspace-scoped. Legacy properties
and onboarding routes redirect after their access checks. Home chooses a saved permitted
workspace, then a saved accessible property or the first property. With none, it shows
a real-data checklist. Invalid or stale context cannot reveal another workspace’s data.
The command palette uses React Aria Autocomplete + Menu; Ctrl/⌘ K opens it. Help lists Ctrl/⌘ ? and Escape. There is no bare global question-mark listener, because the research requires modifiers and staff must be able to type punctuation without opening an overlay. Property recents store at most five IDs in a cookie, never names or guest information. Appearance/language cookies are HTTP-only, validated server actions; sidebar and recent context cookies are nonsensitive browser preferences and are rechecked against access. Theme and density reach the document and provider before paint, including high contrast.
TanStack Table 9.3.1 (MIT, released 12 October 2026), internationalized/date 3.12.4 (Apache-2.0, already pinned transitively with React Aria), and Fontsource Inter 5.3.0 (OFL-1.1) satisfy the seven-day release rule. No paid feature or runtime service is added.
Tradeoffs and limits
Section titled “Tradeoffs and limits”Properties use a create Sheet and a focused edit page with its own URL. A nonmodal intercepted-route editor, server sorting, saved table views and virtualization remain future work. Context selectors load all authorized property pages so search is complete; large portfolios may later need a dedicated server-search endpoint. The SDK and API contracts are unchanged. The existing English technical documentation remains English; new staff interface labels are complete in both shared catalogs.
Organization context is the active organization returned by the existing identity shell. Organization creation/switching and workspace administration are not added here: the current operator UI exposes neither workflow. The setup flow creates one workspace; browser acceptance therefore creates two properties there. Multi-workspace isolation and context switching are additionally covered by the existing PostgreSQL and new shell tests.
Acceptance evidence
Section titled “Acceptance evidence”The local checks run with PostgreSQL 17 and PgBouncer in the gc-shell-verification
Compose project on ports 3910–3912. PgBouncer uses one connection for isolation tests and
two for the existing resident-job tests, matching their harness configuration.
The production web build and Docker image build are both verified. Browser acceptance uses
the built application and synthetic example.com identity data: setup, sign-in, first-run
checklist, two properties, update/reload persistence, channel credentials form, account
popover and command palette. Screenshots and audit results are retained locally under
/tmp/guestcraft-shell, outside version control. Real customer Channex credentials are
not used; injected-transport and existing race tests verify connection behavior.
The component suite includes keyboard selection, dialog/sheet focus return, tooltips, field descriptions and errors, date locale/week-start rules, table sorting and states, status icons/text in IT/EN, RTL, all themes and all three densities. The existing style canaries now render the expanded kit too; visually hidden native Aria inputs are checked through their visible labeled controls rather than treated as pointer targets themselves. The upstream drift suite remains opt-in, as before this change. A human screen-reader audit and operational PMS screens are still future acceptance work.
Final acceptance records 58 screenshot/axe combinations with zero WCAG 2.2 AA violations or page errors, plus three supplemental screenshots for persisted high-contrast preferences, the mobile drawer and documentation. The supplemental docs audit also passes after ensuring that the brand link’s accessible name includes its visible text and inline links remain underlined. Browser checks additionally verify preference reload, sidebar persistence, focus return, denied workspace access, failed-read recovery and sign-out/denied reload.
Shell and settings polish (C1)
Section titled “Shell and settings polish (C1)”Acceptance: keyboard users can distinguish command result types, search settings by human-readable names and save canonical values, recover after a field error without losing a draft, and use setup/sign-in at 390 px and 200% text size. Channels presents one connection status and numbered instructions. Dialogs and Sheets (including the palette and mobile navigation) darken the page in all four themes.
Use a decorative scrim color role, black in every theme, at 40% opacity rather than
the foreground role. It has no text/controls and therefore no contrast pair; the
regression checks compositing darkens every theme surface. All content still uses
the existing contrast-tested pairs.
Property settings use the kit ComboBox with explicit selection, retaining field-linked
server errors, disabled/read-only behavior and canonical API values. Options come from
Intl.supportedValuesOf; currency names use Intl.DisplayNames, and time zones show
IANA cities plus localized generic region names and the current offset from
Intl.DateTimeFormat. Intl has no city DisplayNames type: IANA city spelling remains
searchable alongside localized region names. UTC and stored aliases/withdrawn codes
remain available. Offsets describe the current instant, not a future stay date.
The shell accepts optional commandResults using the CommandResult view model
(id, label, path, Lucide icon, localized typeLabel). Callers own permitted result data
and supply unique IDs; future API search can add types without replacing rendering or
keyboard handling. No reservation/guest search operation or screen is implemented here.
C1 browser evidence lives under /tmp/guestcraft-polish/screens, with built fixtures
and audit details in /tmp/guestcraft-polish. The production app uses real PostgreSQL
and synthetic example.com data on ports 3950–3954, Compose project gc-polish.
The full property option lists are exercised there; component interaction fixtures
use a small set of real Intl codes while the option-generator tests use the full lists.
Auth cards use auto margins within a viewport-height flex layout; tall forms scroll,
and input/grid minimum widths allow the combined 390 px and 200% text-size case.
Review limitation: the real-browser axe audit reports aria-hidden-focus on surrounding
controls while a ComboBox suggestion list is open. React Aria 1.22.1 deliberately calls
ariaHideOutside for the input and popup in useComboBox; Tab closes that popup and
removes the hiding before focus moves to the next visible field. That browser keyboard
path was verified. No rule is disabled and no library accessibility behavior is patched.
This needs a human screen-reader review; closed fields, modal overlays, planning/help,
and auth pages pass the browser WCAG 2.2 checks. Existing component axe tests also pass.