# UI parity — scheduling / availability algebra

This area is a **logic layer**, not a screen. `lib/schedule.ts` has no JSX; it feeds the demo's
day-calendar components and store actions. Our `BookingScheduleService.php` likewise feeds the
Tailwind day view (`dayLayout()` is consumed by `frontend/controllers/BookingController.php` and
`frontend/views/booking/*`). UI parity here = the *states the algebra makes renderable*, not pixels.

## Renderable states the demo logic produces

| State | Demo source | Our source | Notes |
|---|---|---|---|
| Working shifts (incl. multi-shift breaks) | `workingBlocks` schedule.ts:178 | `workingBlocks` BSS.php:108 | Demo reads `WorkingDay.shifts[]` (types.ts:123–132 — multiple shifts model breaks). Ours reads each `UserShift` row; multiple rows for a day = multiple blocks. Equivalent. |
| Off-day (no shifts) | `isWorkingOn` false | `isWorkingOn` BSS.php:135 | Column hidden unless agent holds a booking → then shown **muted**. Parity. |
| Muted column (inactive/off agent with a booking) | `CalendarColumn.muted` schedule.ts:261 | `['muted'=>true]` BSS.php:405 | Drop-target disabled when muted; our blocks also flip `draggable=false` when muted (BSS.php:552). Parity. |
| Shaded out-of-shift / time-off bands | component renders raw shifts+time-off | `shadedGaps` + `timeOffs` BSS.php:597,565 | Ours pre-computes gap intervals + labeled time-off blocks (reason label, note, `shopWide` flag). Ours is more structured. |
| Lane-split overlapping bookings | `packLanes` → `{lane,lanes}` | `packLanes` BSS.php:438 | Side-by-side concurrent bookings. Parity. |
| Now-line (today only, inside window) | (component) | `nowMin` BSS.php:608–612 | Ours nulls it outside the window. |
| Placement-rejection message (drag/create) | `placementReasonText` schedule.ts:352 | `reasonMessage` BSS.php:327 | 4 reasons, ours bilingual (`Yii::t`). |
| Empty day (no agents at all) | columns `[]` | `hasAgents=false` BSS.php:630 | Ours exposes an explicit empty flag for the view. |

## RTL / i18n

- Demo strings are hard-coded English (schedule.ts:355–363) — no RTL/i18n in this lib.
- Ours routes every user-facing string through `Yii::t('frontend', …)` (BSS.php:185, 331–338, 434) → bilingual ar/en. **Ours ahead** on i18n. Verify the four placement strings + "Blocked"/"Walk-in" exist in both `common/messages/{ar,en}/frontend.php` (not confirmed in this pass — flag for the i18n parity check).

## Concrete UI gaps (driven by logic gaps)

1. **Overnight appointments mis-render.** Because ours has no business-day remap (logic.md §2), a 01:00 next-morning booking on an overnight shop will not appear on the prior day's column even though the *shift shading* extends past midnight. Demo renders it at minute 1500. → visible gap on overnight shops only.
2. **Wrong-length blocks when `to_hour` is missing.** Ours falls back to a 60-min block (BSS.php:528); demo always draws true Σ-service duration. → block height/overlap can be wrong.
3. **Free-slot picker (ours-only)** — `freeSlots` powers a reschedule/new-booking time dropdown that has **no demo equivalent**; demo does this by dragging on the grid. Not a regression, but the two UX models differ — our parity target for *creating/moving* a booking is a dropdown of valid slots, the demo's is drag-to-place with live `checkPlacement` feedback. If drag-to-place is the canonical UX, our calendar view should also support drag (confirm in the day-view template, out of scope for this file).

## Not verifiable from the logic layer

Pixel layout, drag affordances, modal copy and the actual day-view markup live in
`frontend/views/booking/*` and the demo's calendar components — out of scope for this algebra
comparison. This file covers only the states the scheduling algebra exposes.
