Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
34e796c
Rename book-as to book-for and fix seat display in view-only zones
sebo-b Jul 3, 2026
db3c93e
code-rev1
sebo-b Jul 3, 2026
55bcb53
Allow releasing own bookings from the bookings table in any zone
sebo-b Jul 3, 2026
8f2b120
Add Phase 2 sprite-assertion e2e for view-only zone display
sebo-b Jul 3, 2026
8905401
Fix book-for release-confinement check for cross-plan conflict zones
sebo-b Jul 3, 2026
96c7ae0
Expose book-for override of an assigned seat in the plan UI
sebo-b Jul 3, 2026
58d7981
Let pure viewers release their own booking from the plan map
sebo-b Jul 3, 2026
dd38364
Keep an own booking blue in a view-only zone when the time isn't exact
sebo-b Jul 4, 2026
dfecf02
Let a zone admin release another user's booking from the plan map
sebo-b Jul 4, 2026
50a4aef
Remove the green assignedOverride seat icon (Phase 3A)
sebo-b Jul 4, 2026
2038b04
Non-exact own booking in a !bookable zone stays yoursChange + pure-sh…
sebo-b Jul 4, 2026
7a49021
Book-for onto a disabled seat (zone-admin override)
sebo-b Jul 4, 2026
234fb62
Document the seat-icon/action invariants + book-for self-asymmetry (P…
sebo-b Jul 4, 2026
af00256
Fix stale "Blue (conflict)" icon rows in FEATURES.md
sebo-b Jul 4, 2026
f8187b8
Phase 3 review fixes: yoursChange legend, doomed book-for Release, di…
sebo-b Jul 4, 2026
878ee8c
Phase 3 review follow-ups + stale viewer action-modal assertion
sebo-b Jul 5, 2026
4dad811
Second-pass review fixes: doomed-action guard on CAN_CHANGE/CAN_DELET…
sebo-b Jul 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ node_modules/
# local scratch / generated artifacts
/graphify-out/
PLAN_*.md
test-results/
.claude/
.opencode/
.pi/
84 changes: 56 additions & 28 deletions AUTOBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ definitions see [GLOSSARY.md](GLOSSARY.md).

- `dates`: a list of `{fromTS, toTS}` slots (absolute unix seconds). There may be
several slots, possibly spanning several days.
- `login` (optional): book _as_ this user instead of yourself (see §3).
- `login` (optional): book _for_ this user instead of yourself (see §3).

**Response** (HTTP 200) is a report with four buckets:

Expand All @@ -43,41 +43,69 @@ overlapping slots in the request or a database conflict at commit time.

## 2. Permission gate (the `autoBook` endpoint)

Auto-book is **always a regular-user action**: the seat it picks is one the
**subject** of the booking could have picked themselves. Roles are equal here —
there is **no** site-admin super-user bypass and **no** confinement to "the zones
the actor administers". The actor's role only governs _who may book for whom_:

- **Booking for yourself** (`login` omitted) → always allowed. Seat selection
acts as you.
- **Booking as another user** (`login` differs) → only a **site admin** or a
**zone admin of at least one zone on this plan** may do this. Once allowed, seat
selection acts entirely as the **target** user (see §3) — the actor's own zones
are irrelevant. A regular user may never book as someone else (`403`, code 104).

The plan must also contain at least one zone the **subject** can book in;
otherwise the request is rejected (`403`, code 104).

> This is intentionally more permissive than **manual** book-as, which also
> requires the actor to administer the _specific seat's_ zone. Auto-book-as only
> requires admin standing on the plan, because the seat it ultimately picks is —
> by construction — always one the target could have booked on their own.
- **Booking for yourself** (`login` omitted) → always allowed, and always a
regular-user action: the seat picked is one you could have picked yourself —
no site-admin super-user bypass, no confinement to zones you merely oversee.
- **Booking for another user** (`login` differs) → only a **site admin** or a
**zone admin of at least one zone on this plan** may do this. Unlike
self-booking, this is a book-for operation performed under the **actor's**
zone-admin authority: the seat pool (and the set of the target's bookings that
may be released to make room) is confined to the zones the **actor**
administers (unconfined for a site admin — see §3). Within that pool the
target only needs to be a **member** of the zone (any role, viewers
included) — not `zone_role ≤ user` — for the request to be accepted at all.
A regular user may never book for someone else (`403`, code 104).
- **This does not relax seat-level assignments.** The algorithm still only ever
picks a seat the target is eligible for by assignment (§3) — auto-book never
auto-places someone on a seat assigned to a different person, for either
self-book or book-for. That override is manual-book-for-only, where an admin
deliberately picks the exact seat with full knowledge of what it's assigned
to (PERMISSIONS.md §8) — auto-book's heuristic has no such context and must
not silently displace a third party's dedicated desk.

The plan must also contain at least one eligible zone for the request (the
subject's own zones for self-booking; the actor's administered zones,
intersected with the target's membership, for book-for); otherwise the request
is rejected (`403`, code 104).

> This mirrors **manual** book-for (PERMISSIONS.md §8) only on the zone-admin/
> membership gate: both require the actor to administer the zone and only
> require target membership, not `zone_role ≤ user`. It does **not** mirror
> manual book-for's assignment override (see above and §3).

---

## 3. Whose seats, whose access

`runAutoBook(login, pid, dates)` selects seats that the **subject `login`** is
allowed to book — and **only** those:
`runAutoBook(login, pid, dates, allowedZids, releaseZids, is_book_for)` selects
seats from:

- enabled seats, in non-disabled zones, where the subject has `zone_role ≤ user`
(this includes zones they administer, since admin ≤ user numerically); **plus**
- enabled seats, in non-disabled zones, where the subject has a
`user_to_zone_roles` row for the zone — and, **unless `is_book_for`**, that
role must be `≤ user` (this includes zones they administer, since admin ≤
user numerically); **plus**
- all enabled seats in **public-book** zones (everyone may book those).

This is the subject's _regular_ access — exactly what they would get if they
clicked "find me a seat" themselves. For a book-as call the subject is the
**target** user, so the target can never be placed on a seat they could not have
booked on their own. The actor's roles never widen (or narrow) this pool.
For self-booking this is exactly the subject's regular access — what they'd
get clicking "find me a seat" themselves, scoped to the zones they can
actually book in (`is_book_for` is false, so the `≤ user` filter applies).

For book-for, the **subject** is the target user, `is_book_for` is true, and
the seat pool is additionally filtered by `allowedZids` to the zones the
**actor** administers (`releaseZids` confines which of the target's existing
bookings may be displaced the same way — see §2). Within `allowedZids`, the
`≤ user` filter is dropped: the target merely needs a membership row, so a
viewer in one of the actor's administered zones is a valid auto-book target
there. The target is never placed in a zone the actor doesn't administer, and
never in a zone they aren't at least a member of.

`is_book_for` only affects that zone-role gate. The seat-level assignment
classification below it (`seatInfo`: `none` / `direct` / `everyone` /
`blocked`) is computed identically for self-book and book-for — a seat
assigned to a login other than the subject, with no `everyone` row, is
`blocked` and excluded from the candidate pool either way. Only **manual**
book-for (`apply()`) skips the assignment check; the auto-book heuristic never
does.

All of this is scoped to the **plan named in the request** (`pid`) — i.e. the
plan currently open in the UI. Auto-book only ever considers the zones and seats
Expand Down
38 changes: 21 additions & 17 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ WARP has two independent role layers: **account-level** roles and **zone-level**

| Role | Value | Description |
|-------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------|
| **Admin** | 10 | Full system access: user/group/zone management and reports. Zone-level actions (e.g., "Book As", enabling/disabling seats) still require a Zone Admin assignment in that zone — which admins can grant themselves. |
| **Admin** | 10 | Full system access: user/group/zone management and reports. Zone-level actions (e.g., "Book For", enabling/disabling seats) still require a Zone Admin assignment in that zone — which admins can grant themselves. |
| **User** | 20 | Regular user. Can book seats in zones they are assigned to. |
| **Blocked** | 90 | Cannot log in. Account exists but is disabled. |
| **Group** | 100 | Virtual account representing a user group. Not a real person; cannot log in. |
Expand All @@ -112,7 +112,7 @@ Each user (or group) can be assigned a role **per zone**:

| Role | Value | What they can do in the zone |
|----------------|-------|---------------------------------------------------------------------------------------------|
| **Zone Admin** | 10 | Assign/unassign users to the zone and to seats, enable/disable seats, book on behalf of any zone user ("Book As"), see disabled seats. |
| **Zone Admin** | 10 | Assign/unassign users to the zone and to seats, enable/disable seats, book on behalf of any zone member including viewers ("Book For"), see disabled seats. |
| **User** | 20 | Book, update, and delete their own bookings. |
| **Viewer** | 30 | See the zone map, seats, and other people's bookings, but **cannot book**. |

Expand Down Expand Up @@ -332,10 +332,9 @@ Accessible via the user icon on the Zones management page.
| Yellow (assigned) | Seat is assigned to others, you cannot book (visible only to non-admins) | — |
| Blue | You already booked this seat exactly for the selected time | Remove |
| Blue (rebook) | You booked this seat, but for a different time range — can update | Update |
| Blue (conflict) | You booked this seat, but another user has a conflicting booking — cannot update, only remove | Remove |
| Red (taken) | Booked by someone else or unavailable | — |
| Red (taken) | Booked by someone else, or your own booking overlaps theirs on this seat (same icon either way) | — (Release your own overlap; zone admins can also release the foreign booking) |
| Gray (disabled) | Seat is disabled (visible only to zone admins) | Edit (toggle enabled) |
| Gray circle / gray person (view-only) | Seat is in a view-only or disabled zone you cannot book in (free vs. taken) | — |
| Gray circle / gray person (view-only) | Not available for you to book: no dates selected, disabled, or a free seat in a view-only zone | — |
| No icon | No date/time selected | — |

### 7.3 Booking a Seat
Expand Down Expand Up @@ -415,19 +414,20 @@ The UI modal surfaces this as sections titled "Booked", "Could not extend or reb
The old "Already booked in another zone" section is gone; an existing booking on the same plan for the same slots takes top priority at step 1 instead.

### 8.4 Auto-Book for Zone Admins
- Zone admins can use the "Book As" feature with auto-book to find a seat for another user (see §9).
- When doing so via auto-book, the seat is chosen exactly as it would have been for the target user themselves (the actor's own zones do not restrict the choice). Manual "book as" is still scoped to the specific seat's zone adminship.
- Zone admins can use the "Book For" feature with auto-book to find a seat for another user (see §9).
- The seat pool is confined to the zones the **actor** administers on that plan (unconfined for a site admin). Within that pool, the target only needs to be a member of the zone — any role, including viewer — for the request to be accepted. Manual "book for" is likewise scoped to the specific seat's zone adminship, with the same membership-only requirement on the target.
- Unlike manual "book for", auto-book does **not** override seat-level assignments: a seat assigned to a different person is never auto-picked for the target, whether self-booking or booking for someone else. Only a manually-picked seat (an admin deliberately choosing that exact seat) can override an assignment.

---

## 9. "Book As" (Zone Admin Feature)
## 9. "Book For" (Zone Admin Feature)

- A "Book As" input field appears in the plan-view side panel (the booking map) for zone admins.
- It is an autocomplete field listing all users with access to the zones on that plan (resolved through the `user_to_zone_roles` view — the single source of truth). For a public zone that is every non-group user, including blocked users (an admin can manage and book on behalf of blocked users); for an enabled zone it is the explicitly assigned users.
- Selecting a user switches the entire plan view to show what that user sees, including their bookings and conflicting bookings across the plan.
- A "Book For" input field appears in the plan-view side panel (the booking map) for zone admins.
- It is an autocomplete field listing all users with access to the zones on that plan (resolved through the `user_to_zone_roles` view — the single source of truth). For a public zone that is every non-group user, including blocked users (an admin can manage and book on behalf of blocked users); for an enabled zone it is the explicitly assigned users, including viewers.
- Selecting a user switches the entire plan view to show what that user sees, including their bookings and conflicting bookings across the plan. Seats in zones the admin administers show real booking actions even if the target is only a viewer there — booking-for overrides the viewer restriction, any seat-level assignment, and a seat-level disable for that seat. A seat assigned to someone else renders plain green "available" under book-for; a seat assigned to the target beyond their days-in-advance window renders blue "availableAssigned"; a seat the admin has disabled keeps its disabled icon (grey X) as a visual cue but is bookable under book-for — click offers Book, or Update when the target already has a conflicting booking in the zone group. This override is book-for-only: an admin booking **for themselves** sees a disabled seat as not bookable and must re-enable it first (self-booking is the regular user flow — the picker's own-login entry is the exit from book-for). The zone-type DISABLED block still rejects book-for outright — override the seat, not the zone.
- When the admin books, updates, or removes a booking, it is performed **on behalf of the selected user**.
- The admin can also auto-book for the selected user.
- Clearing the "Book As" field (pressing Enter while empty) reverts to the admin's own view.
- Clearing the "Book For" field (pressing Enter while empty) reverts to the admin's own view.

---

Expand Down Expand Up @@ -465,9 +465,9 @@ The old "Already booked in another zone" section is gone; an existing booking on
## 11. Bookings List & Report

### 11.1 Bookings List (available to all users)
- Shows **future** bookings in zones the user is assigned to.
- Shows **future** bookings in zones the user is assigned to, plus the user's own future bookings in zones they no longer have access to (so they can still release them — the plan map can't reach a seat in a zone they can't open).
- Columns: User name, Plan, Seat, Time (merged from/to into one column).
- A **delete button** (🗑) appears for bookings the user can remove (own bookings where they have User role, or any booking where they have Zone Admin role).
- A **delete button** (🗑) appears for any of the user's **own** bookings (regardless of their role in that zone — viewers and users who have lost access can still release their own), plus any booking in a zone where they have **Zone Admin** role.
- Filtering by user name, plan, seat, and date range.
- Sorting by time and user name.
- Paginated with remote data loading.
Expand Down Expand Up @@ -795,19 +795,23 @@ menus) switches between them, showing a **moon** icon in light mode and a **sun*
| Change own password | ✅³ | ✅³ | ✅³ | ✅³ |

¹ System Admins do not automatically hold zone-level rights. Zone-level actions require the corresponding zone role (User or Zone Admin) in that zone — which a System Admin can always grant themselves via zone user assignment.
² Viewers can remove their own bookings (even from zones they are only a viewer in), to clean up leftover bookings after role changes.
² Viewers — and users who have lost access to a zone entirely — can remove their own bookings there, to clean up leftover bookings after role changes.
³ Only available with built-in authentication (not SSO).

---

## 27. Plan View Interaction Summary

| Seat State | No Dates | Green (Book) | Green (Rebook) | Blue (Update) | Blue (Conflict) | Blue (Exact) | Red (Taken) | Yellow (Assigned) | Gray (Disabled) |
| Seat State | No Dates | Green (Book) | Green (Rebook) | Blue (Update) | Red (Taken, own overlap) | Blue (Exact) | Red (Taken, foreign) | Yellow (Assigned) | Gray (Disabled) |
|-----------------------|----------|-----------------------|----------------------|---------------|-----------------|--------------|-------------|-----------------------------|-----------------------------|
| **User actions** | — | Book | Book (replaces) | Update | Remove | Remove | — | — | — |
| **Zone Admin actions**| — | + Book As, + Edit | same | same | same | same | + Edit | + Edit | + Edit |
| **Zone Admin actions**| — | + Book For, + Edit | same | same | same | same | + Release, + Edit | + Edit | + Edit |
| **Viewer actions** | — | — | — | — | — | — | — | — | — |

A viewer (view-only access to every zone on the plan) can still **release their own booking** from the plan map, and **shorten** it (Update) when the selected time is fully contained within the booking — a pure shrink is always allowed, even in a view-only or disabled zone. Clicking a non-exact own booking opens the action panel with Release, plus Update when the selection is a pure shrink; non-actionable seats (free, taken by others, assigned) open no panel. The auto-book button and book-for input stay hidden in viewer mode.

A zone admin can **release another user's booking** from the plan map by clicking a taken seat in a zone they administer — the action panel offers Release (apply() allows it via the per-seat zone-admin check for foreign removes). Non-admins get no action on a taken seat.

---

## 28. Configuration Reference
Expand Down
Loading