0025. Render a data-agnostic planning grid with explicit creation intents
- Status: Accepted
- Date: 2026-10-27
Context
Section titled “Context”The front desk needs a readable room/night tape chart. The UI kit and reservation backend are being developed separately. Research §4.1 and §5.3, ADRs 0007, 0013 and 0018 require half-open stays, keyboard and non-drag alternatives, semantic status cues, local dates, and bounded rendering at 500 units × 60 nights.
Decision
Section titled “Decision”Build PlanningGrid inside packages/ui/src/planning, with opaque view-model
identifiers, Temporal.PlainDate dates and parent-owned callbacks. No routes,
API calls, permissions lookup or persistence are implemented. The parent maps
assignment segments, supplies the property’s today, authoritative availability
and locale, and handles creation/detail workflows and server conflicts.
Bars represent one segment and span arrival midday to departure midday, with
clipping markers. Occupancy remains [arrival, departure). Unassigned overlaps
are packed into separate tracks per type so every stay remains visible and each
track can be virtualized. Invalid keys, dates, counts and assigned overlaps throw
generic programmer errors without including supplied values. Missing inventory
is labelled unknown, not zero. Free cells only prove physical vacancy; the parent
must check sellable inventory and permissions before saving.
Creation uses two clicks/taps, a mouse drag across one unit’s free nights, or Shift+horizontal arrows followed by Enter; the end cell is the last occupied night. Escape, pointer cancellation and leaving the grid cancel a drag. The component rechecks current occupancy at confirmation. Creation is disabled by default and requires explicit permission plus a callback. Bar opening remains available in read-only mode. Bars never move or resize.
Use an APG data grid on native table/row/header/cell markup, roving focus, indexed virtual rows, visual-direction arrow navigation and a pinned active row. F2 focuses a stay bar for its React Aria tooltip; Escape dismisses it. React Aria also supplies actions and the one-click legend dialog. IT/EN texts are shared catalog entries; styles and geometry are centralized on semantic tokens.
Use MIT TanStack Virtual 3.14.21, published 16 October 2026. The research’s 3.14.22 release is too recent for the seven-day dependency policy on this date; the older maintained patch meets that policy without exceptions. Row virtualization with five-row overscan bounds mounted rows and bars. Sixty columns remain small enough to render directly. Touch targets expand row geometry to 48px where needed; requested desktop densities remain 28/36/44px.
Consequences and acceptance
Section titled “Consequences and acceptance”The supported component workflow is inspecting availability/housekeeping and stays, opening a stay, and requesting a validated physical range. Tests cover calendar geometry, clipping/turnover, availability and overbooking, unassigned packing, keyboard/pointer alternatives, read-only behavior, invalid input, recovery, RTL/localization, WCAG 2.2 axe and bounded rows/bars for 500 × 60. Built static/hydrated fixtures provide screenshots in IT/EN, light/dark, desktop, tablet and mobile; fixture sources and images stay outside the repository.
This is partial product functionality: operator pages, API/SDK mapping, authorization and persistence are intentionally absent. The parent must provide an equivalent list/print view and defer disruptive real-time reordering while selecting. Screen-reader sessions with NVDA/VoiceOver and a CI INP benchmark remain release work. The integration round must review the new component props and re-theme its style map alongside the replacement kit.
Planned separately: a “Move…” dialog, moving/resizing bars, split-stay actions, OOO/blocks and out-of-service lanes, filters and guest/reference search. No successful mock, screenshot or coverage number implies a complete PMS module.
C1 integration polish
Section titled “C1 integration polish”When the measured grid viewport is below 640 px, the pinned unit column is 96 px instead of 164 px. Geometry and focus scroll padding use the same measured width; this also supports narrow embedded panels. Unit names truncate while housekeeping icons and letters remain visible. Nights retain their minimum and touch target sizes. The visible hint is one truncation-safe line; full writable/read-only keyboard and pointer instructions are in the existing Legend and help dialog. Its modal uses the kit’s theme-independent decorative scrim. Stay, availability, weekend, today and focus colors retain the shared semantic roles and contrast inventory in all four themes. This remains a reusable component, with the product gaps above unchanged.
Moving bars (ADR 0042)
Section titled “Moving bars (ADR 0042)”ADR 0042 replaces “bars never move”: a writable grid lets
bars marked move be dragged and emits onMoveStay intents, which the app confirms in a
dialog before saving. Resizing, a keyboard move mode inside the grid and the list/print view
remain planned; the app’s Move and Split dialogs are the non-drag alternatives.