Every click a booker makes posts its own request. Three surfaces do it: the offer steppers on a slot page, the pool steppers on the seat map, and clicking a place on the map.
Only the slot page coalesces them. Its stepper waits before posting, so a run of clicks becomes one request, keeps a single request out at a time, and reconciles the answer it gets: it compares what the visitor asked for against what was actually posted, so a quantity the server corrected is applied, while a click that landed after the request left is sent again rather than lost. It also carries a watchdog for an answer that never arrives.
The seat map has none of that. It guards on a busy flag that drops a click while a request is out, so clicking quickly loses clicks and says nothing about it. And a booking of several hundred places is several hundred requests, each taking the order lock in turn.
The two steppers also disagree by construction: each builds its own buttons, with its own classes, its own keyboard and screen reader handling, and its own idea of what a refusal looks like.
Proposed resolution
Add a yoyaku_presentation submodule, the way orchestra_presentation holds the markup and styles the Orchestra surfaces share. It owns the stepper widget: the markup its script builds, the styles, the accessibility rules, and the queue that posts on the widget's behalf. Markup and behaviour belong together here because the widget builds its own markup.
- Move the slot page stepper into it, with its behaviour unchanged.
- The seat map's pool rows drop their hand-rolled stepper and use the widget, so every stepper on the site shares one delay, one refusal message and one set of accessibility rules. The delay is named once, instead of sitting in each file as a bare number.
- Clicking a place is not a stepper, but it needs the same coalescing, so the queue is usable on its own.
- The place operation endpoint takes a list of places, applied atomically: one lock, one answer, one refusal list. The single place operation is replaced rather than kept alongside it.
- A click paints at once and is reconciled when the answer lands. A refusal names the place and says why on screen, which a place click already does and a stepper now does too.
- Functional JavaScript coverage for the shared widget on both surfaces, including the keyboard path and the refusal message.
User interface changes
A run of clicks becomes one request. A click made while a request is out is no longer dropped. Both steppers refuse the same way, and say so.
API changes
The place operation carries a list of places instead of one. Before 1.0, so the single place operation goes away rather than staying as a fallback.
Issue fork yoyaku-3615940
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
Comment #2
mably commentedAdd to the scope: the notice a refusal raises must be dismissable. It dismisses itself after a few seconds today, and while it is up it covers the controls underneath, so a booker who has read it has to wait it out.
Clicking it should close it. It also needs a way there for a keyboard user, so Escape while it holds focus, or a real close control rather than a clickable region.
Since one widget will own the notice, this is one rule in one place rather than per surface.
Comment #5
mably commented