Problem/Motivation

The rule from #3616316: Refuse a booking that orphans a place, alone between bookings or at the end of a row refuses a booking that would leave a place empty on its own, but nothing stopped a booker opening such a hole by giving a place back. Found on the seat map, and reproduced with a probe on the shipped code:

  • hold A1, then A2, then A3, all clicked on the map, then release A2: accepted, and A2 is left empty between two bookings.
  • for contrast, taking A1 and then A3 is refused, which is the same end state reached from the other direction.

So the rule policed the way in and not the way out, and a row ended up in exactly the state the venue attached the policy to prevent.

Why a plain refusal is not the answer

A refusal can leave somebody unable to reduce their booking at all. A party holds A1, A2 and A3, and a stranger holds A4:

  • release A2 and it is left on its own, refused;
  • release A1 and it sits between the row's edge and A2, refused;
  • release A3 and it sits between A2 and the stranger's A4, refused.

Every release is refused and the only way out is cancelling the whole order, which is worse than the hole.

What it does instead

The click is honored whenever it strands nothing. When it would, the same number of places still goes back, but one of the run's extremities goes instead of the place that was clicked, and the booker keeps what they clicked on. Nothing else moves: no place the booker holds is relocated, so a click never rearranges an evening.

Which extremity is a ranking, in this order: an end that strands nothing at all, then the end that leaves the larger stretch of places available side by side, then the end nearer the click. Where both ends strand something, the wider gap wins, because a wider gap is the one another booker can still use. Where no extremity is acceptable either, the release goes through as asked rather than trapping the booker.

The measurement is the one PlaceOrphans already makes for the hold, so the way in and the way out cannot disagree, and the number comes from the policy attached to the session through PlaceSpacingRule, so a venue that never attached the rule sees none of this.

What the booker is told

A place they did not click has gone back, so the map says why, in the register of the existing "Your places have moved so that you can sit together":

We gave back one of your end places instead, so yours stay side by side. A single empty place is rarely booked.

A booker left holding a single place gets the sentence that fits, since nothing is beside it to stay beside:

We gave back your other place instead. A single empty place is rarely booked.

The map now has one message path rather than a device per message, with a level that carries its own styling, a duration a venue can set, and a dismiss button reachable by keyboard. A message is announced politely when it is news and assertively when something was refused.

What was considered and dropped

  • Repacking the party after the release: it moves places the booker did not click, needs a search of its own, and the decision in #3615212: Reducing a split party leaves the hole inside it, free for a stranger to book not to repack on the map's release path stands.
  • Refusing the release: the dead end above, and it would need a locking section the release path does not have.
  • Giving the booker's whole run back: far too violent for one click.
  • A modal dialog: an inline notice says the same thing without taking focus away from the map.

Remaining tasks

  • The release paths with no map, where nobody is watching: the API, an operator screen and a partial cancellation can still open a hole. The funnel is BookingManager::release(), not the thirteen call sites, and it locks the booking row rather than the slot row, so a rule enforced there brings a locking section with it. To be filed on its own.

User interface changes

Clicking a held place on the map to give it back may hand back another place from the same run instead, with a notice saying why. Notices can be dismissed, and how long they stay is a setting.

API changes

PlaceOrphans::releaseInstead() answers which place to give back, and PlaceSpacingRule reads the fewest places a session allows to be left side by side from whichever attached policy declares it. Nothing released depends on either.

Data model changes

None. One setting is added for how long a map notice stays.

AI-Generated: Yes (the maintainer found this on the seat map and steered every design decision. Claude Code wrote the code, the tests, the documentation and the French strings, and ran them.)

Issue fork yoyaku-3616353

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Issue summary: View changes
mably’s picture

Title: Refuse a release that leaves a place empty on its own, and always allow giving back a whole block » Consolidate the party after a release that would leave a place empty on its own, and explain the move
Issue summary: View changes
mably’s picture

Issue summary: View changes

mably’s picture

Status: Active » Needs review
mably’s picture

Title: Consolidate the party after a release that would leave a place empty on its own, and explain the move » Give back an end of the run instead of the clicked place, so no place is left on its own
Issue summary: View changes

  • mably committed a4e5ff4b on 1.x
    fix: #3616353 Give back an end of the run instead of the clicked place,...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.