Issue #3618603: Pre-alpha3 audit: give the calendar day grid an accessible name and state, throttle every path that places a hold, and refresh the figures, vocabulary and pages the last seventy commits moved

Audits [#3618603].

What is here, in the order the issue lists it.

1. The calendar day grid. Each day is named in full and its state is a word: aria-label carries the weekday, the date and whether it can be booked, because the number in the cell was everything a sighted visitor needed and nothing a reader could use. aria-pressed says whether a day is chosen. A day that cannot be booked carries aria-disabled instead of disabled, for the reason stepper.js and venue-map.js both give in their own comments, and pressing it announces which day it is and what is wrong with it. The validity note becomes the one live region (role="status", built once and rewritten, never replaced), the submit is marked rather than switched off and describes itself through that region, the month arrows get names, the weekday initials are hidden from the reader that does not need them, and the stylesheet gets a focus ring. BookingCalendarTest gains a scenario for the naming and the region, and its existing assertions move from disabled to aria-disabled. Run in a browser: 53 assertions, green.

2. One throttle for every path that places a hold, and it ships OFF. HoldThrottle in the engine module, asked by the booking form, the offer card's claim and the place map's click, on one flood event with one setting. It deliberately does not live in BookingManager: the engine holds under cron and in a queue worker, where there is no client to count. The setting moves from yoyaku_calendar.settings to yoyaku.settings as hold_flood.limit and hold_flood.window, because the place map must not depend on the calendar to be capped; the calendar's settings screen had nothing else on it, so the form, its route and its menu link go, and the two fields appear on the site booking settings screen beside the rest. The endpoints answer 429 with a sentence a booker can read. Giving places back is never throttled: PlaceSelectionController asks only where the request names something to take, since refusing a release would leave a booker holding places they asked to be rid of.

The cap is nought as shipped, and that is the considered position rather than a retreat. A check here cannot stop a flood: by the time it answers, the site has bootstrapped, built its container, resolved the route and decided access, so what a refusal saves is the lock and the write at the end. Anything big enough to hurt has to be turned away upstream. So it is a policy about one misbehaving client rather than a defence of the machine, and it counts per ADDRESS, where a wrong guess lands on the wrong person: a school or a town hall is a crowd of legitimate bookers sharing a count, meeting the cap during exactly the rush when refusing costs most. An operator who has seen a client draining capacity turns it on, and the setting says so where they read it. Nothing about how much anybody may book depends on it.

That also retired the ten a minute the form had carried since it was written, which the browser tests showed to be a number a PERSON reaches: a form submits once per booking, but the offer card and the seat map post once per run of clicks, so picking a party seat by seat is dozens of attempts a minute. Ten was never generous, it was untested.

ClaimThrottleTest is new and was seen to fail against the unfixed endpoint ("Failed asserting that 200 is identical to 429") before it passed against this branch; it now also asserts that a site on the shipped configuration is not throttled at all. PlaceBookingTest gains the take-is-capped, giving-back-is-not pair. Ten classes that drive a booking surface declare, through a trait, that they are not about the cap, including the two rigs that fire many requests from one address on purpose: a seat-picking test should not be able to fail on a rate limit.

3. The cost register. A second dated column in PERFORMANCE.md, measured on this head with the cost tests' own instrument, keeping 2026-08-12 beside it: a hold costs one statement more and seating three, both of them the anchor counters [#3617101] introduced, while the reads of the place table fall from five to one and the orphan rule costs five fewer. The hold figures were re-run on MySQL and came back identical, statement for statement, so the column is not the driver. The sentence explaining the read that does not grow is corrected: what the test counts is the counter, not the grouped sum it replaced.

4. The pages that went stale. docs/performance.md says which head its figures predate and which four issues moved them, since it is measured from before the locking work and an operator sizes a rush from it. docs/metrics.md is regenerated (1,116 files to 1,262) and now says to regenerate it as a release step, which is what it has needed for three cycles. Seven "used to" passages go. The README gains the yoyaku_presentation row it was missing and describes the lock as it is taken now.

5. Vocabulary. Around fifty money words in the tests and the load harness that [#3618369] did not reach, including a class named SeatSoldOnceTest, a scenario naming a "sold out session", and the rig's printed column header; plus oversell in shipped engine code. And some nine hundred comment lines that said session where they meant the slot, which is the one word this codebase cannot spare: it is the visitor's own session everywhere else. Swept by hand where the meaning was the visitor's, and the French carries onto every reworded string rather than being written again.

6. Tooling. _LENIENT_ALLOW_LIST names the development dependencies that do not declare Drupal 12 yet. This is a prerequisite and not the whole answer, and the lane still does not build. Played on this branch, the job log shows the list applied ("Using lenient allow list: drupal/orchestra, ...") and composer still gives up on drupal/orchestra[dev-1.x] require drupal/core ^11.3: the lenient plugin did not relax that constraint. So the lane unblocks when orchestra itself declares the major, which is being done separately, and this change is what stops the list being the next thing to discover. Recorded rather than claimed, because an empty bot diff and a lane that cannot build look identical from outside. The phpstan gate moves from the templates' level 0 to level 1, which this module already meets with nothing reported. A breadcrumb builder declares the CacheableMetadata argument its interface requires in the next major, which removes a deprecation from every page it is asked about.

7. Smaller. BookingSlot's rule base field, its getter and its interface method go: provisioned for yoyaku_recur, which is unbuilt, and nothing wrote or read it. ModuleBoundariesTest gets the process isolation every other class has. Three files stop ending with two newlines.

Checked before pushing. phpcs over the module root under Drupal,DrupalPractice: no error, and no warning outside the Markdown long lines CI does not scan. phpstan level 1: nothing. prettier and stylelint on the touched JavaScript and CSS: clean. eslint: no error. cspell over the changed files: one coined word of mine, reworded rather than added to the dictionary. scripts/check-translations.php: every shipped string translated, and a literal search back the other way finds no dead entry. ClaimThrottleTest, PlaceBookingTest, CalendarBookingFormTest, BookingCalendarTest, ModuleBoundariesTest and the nine classes the vocabulary pass touches: green, the first three on both drivers.

Not done here, and why. The load rig has not been re-run on this head: that is a machine exercise rather than a code change, and the page says so rather than implying otherwise. Raising phpstan past level 1 is its own issue: level 2 reports 264 and level 3 reports 356, but 219 of those are one pattern, a call on a variable typed as EntityInterface, and a drupal.entityMapping block takes level 2 to 158 on its own.


Second commit, after the first pipeline. CI found two things, and one was worth more than the test it broke: the default cap above (a number a person reaches), and a fieldset that does not nest what it groups unless told to, so the two new settings fields posted flat while the submit handler read them nested, wrote nought for the window, and nought is a window the schema refuses. The whole booking settings form stopped saving, and BookingSettingsFormTest caught it. Named flat now, as the rest of that screen already is. Re-verified locally: BookingSettingsFormTest 34 assertions, OfferStepperTest 82, VenueMapPickerTest and PlaceMapPerformanceTest 321 together, the three throttle-touching kernel classes on both drivers.

Edited by Frank Mably

Merge request reports

Loading
Loading