Skip to content

Building a venue map

This is a walkthrough for building a seating plan: the artwork behind it, the coordinates that put a seat on it, and the entities that decide how each area is offered. It reads top to bottom. For what the module does with a finished plan, read Placement; for the file format that moves one between sites, read the yoyaku_placement_io README.

A plan needs two things: a drawing of the hall and a coordinate for every place. Both come out of one trip through a vector editor, described in section 2, so neither is a prerequisite for the other. Everything else is entered or imported.

1. The artwork

The venue's Place map background is one SVG file. It is the picture of the hall: the stage, the walls, the floor edges, the row letters, whatever helps a visitor recognize where they are sitting.

You can draw it by hand, as the bundled example was, or let the layout trip in section 2 produce it: what you draw around the markers there is what becomes this file. Either way, what it has to contain is the same.

It is a document, not an image

The picker fetches the file and injects it into the page, rather than referencing it as an <img>. That is not an implementation detail you can ignore, because two things depend on it:

  • each seat is drawn as a <use> reference to a <symbol> the artwork defines;
  • the plan is drawn at the artwork's own scale, so a shape the plan draws over it lands where the artwork says it should.

A use resolves only inside the document that holds it. So the file has to be a standalone SVG that stands on its own, and anything the plan points at has to be in it.

What it must contain

A viewBox. This is the single most important line in the file, because it defines the coordinate space that every place's x and y is expressed in. The bundled example uses:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2210.2 2252.0">

so its seats have coordinates between 0 and about 2210 across and 0 and about 2252 down. A place at x: 727.8, y: 364.8 lands on the artwork with no transform of any kind. Get the viewBox wrong and every seat is in the wrong place at once.

A <symbol> for each place sprite, inside <defs>. The venue's Place sprite id and Accessible place sprite id fields hold their ids, without the leading hash. The example defines two:

<defs>
  <symbol id="place-glyph" viewBox="0 0 32 32">...</symbol>
  <symbol id="place-accessible" viewBox="0 0 32 32">...</symbol>
</defs>

A sprite id naming a symbol the artwork does not define is the most common way to end up with a hall of invisible seats: the plan draws, the seats are there in the markup, and nothing appears.

Nothing for the areas. An area offered as a pool is clicked directly on the plan, and the shape it is clicked by is not in this file: it is stored on the section, one outline per part of it, and the plan draws it over the artwork in its own layer. So the artwork is only the picture of the room.

Note

That is why the bundled example needs no ids on its shapes. Its artwork is line work, text labels and a few rectangles, and it does not have to name any of them: the areas are arranged in the layout trip below, which writes their outlines where the plan reads them.

What to leave out

The seats. The picker draws every seat itself, from the coordinates. Seats drawn into the artwork will sit under the real ones and cannot be clicked.

The example is a good model of how little the file needs: 132 lines, 96 text labels, four rectangles and one path, for a hall of 1,422 seats.

2. Coordinates for every place

Arranging the hall is one trip through a vector editor, from the venue's Layout tab. Four steps, and the third is the only one that is work.

  1. Generate the places. Sections, place grades and then Generate places on the Places tab: rows times places per row, per section. Nothing here knows where anything sits yet.
  2. Download the layout. Every place is a marker in the file. A place that already has coordinates is drawn where it sits; a place that has none is parked on a grid below the room, one line per row, under the name of its section.
  3. Arrange it. Move the markers into the shape of the room, and draw the room around them or trace a floor plan under them.
  4. Upload it back. The coordinates and the angles are read off the markers, and what is left of the file becomes the artwork from section 1.

For a plain rectangular room, the grid from step 2 is already the finished map and step 3 is nothing at all.

The trip is re-entered whenever the hall changes. Downloading again draws every place where it now sits, over the current artwork, so adding a row is download, drag the new row in, upload. Applying the same file twice writes nothing.

A first room, step by step

The tab is at Administration › Booking › Venues › your venue › Layout, between Places and Numbering. The line under the steps is the one to read first: it says how many places the venue has, how many of them are placed, and whether there is already a background to be reused.

1. Sections, grades, then places. Add a section per physical area of the room and a place grade per band you intend to price. Then use Generate places on the Places tab, once per section: a list of rows (A-M, or 1-12) and a number of places per row.

Note

A pooled area is drawn like any other. The layout carries every place of the venue whatever mode its section is in, because the mode is a per-production decision: the Parterre is a pool for one concert and seated for the next, so its coordinates are worth having either way. What a pooled area needs as well is an outline, which is the shape a booker clicks to take a place in that part of the house. It is arranged in the same trip, in the Areas layer.

2. Download. A few things to look at, then Download the layout.

What to arrange decides which halves of the hall the file carries: the places, the areas, or both. Placing a hall and shaping its areas are two different sittings, and a file holding only the one you are working on is far smaller and has nothing in it to grab by mistake: for the bundled Auditorium, the areas alone are 30 kB against 400 kB for everything. Whichever half you leave out is left alone on the way back in, so an areas-only file cannot move a seat and a places-only file cannot reshape an area.

Space between places and Space between rows are the pitch of the grid the unplaced places are parked on, in the artwork's own units. They matter more than they look: for a plain rectangular room the grid is the finished map, so these two numbers are that room's real seat spacing and nobody but you knows them. For a hall that already has coordinates they are prefilled with what it measures, so there is nothing to decide.

Letter each marker with its place number writes A12 on each marker. It is on by default, and off above a few thousand places, where the lettering doubles the objects your editor has to hold and you are arranging sections rather than reading numbers.

Write each area name across its shape puts each part's name in the middle of its outline, so a shape says which part of the house it stands for while you drag it. On by default. The text is taken back out on the way in and written again on the next download, so it never ends up in the picture a visitor sees.

Fill each marker with the color of its grade paints the markers in the same colors a booker sees, so the bands are legible while you arrange. It is on by default wherever the venue's grades name colors, and worth turning off when you are tracing a floor plan, where one flat color stands out better. Each grade gets one stylesheet rule and each marker a matching class, which has a useful side effect in the editor: since each grade is a distinct fill, Edit › Select Same › Fill Color selects a whole band in one click, which is what you want before rotating or nudging one.

Every marker also names its grade in data-yyk-grade, by the grade's machine key. That is what an upload can read back, and the color is not: recoloring a marker changes how the file looks and nothing else.

Marker size is not asked for. A marker stands for one seat, so it follows from the space between places.

What comes out: every placed place drawn where it sits, and every unplaced one parked on a grid below the room, one line per row under the name of its section. On a room with nothing placed yet the file is therefore taller than the room. The page says so when it happens.

3. Arrange it. Open the file, move the markers into the shape of the room, and give the room its picture: draw the stage, the walls and the aisles, or paste the architect's floor plan onto a layer underneath and trace over it. Everything you draw is kept. Everything you must not do to a marker is in the next section.

The file arrives as four layers rather than as groups, so a marker is selectable where it lies: click a chair and drag it, with nothing to enter and nothing to ungroup. They split by two questions, who drew it and whether the picker still needs it afterwards.

Kept in the artwork, which is what a booker's page ends up showing:

  • Room — everything you draw: the stage, the walls, the aisles, the doors. It arrives empty, nothing is ever written into it, and it sits below the seats so a stage drawn in it goes under them rather than over.
  • Labels — the section and row names, drawn once for each place being parked. Yours from that moment: move them, restyle them, delete them, and a later download will not put them back. A hall being added to gets a New labels layer for whatever is being parked this time, so the arrangement you already made is never rewritten.

Taken out on the way in, being scaffolding for the arranging session only:

  • Places — the markers. The picker draws every place itself from the coordinates it has just read, so markers left in the picture would draw the hall a second time, underneath the real one.
  • Areas — one shape per part of the hall, seeded around that part's own seats, or as a box below the room for a part that has no seats to derive one from. Drag and reshape each one over the part of the house it stands for. The picker draws them from what it reads here, in its own layer over the artwork, so an area can be clicked without the artwork naming anything.
  • Place numbers — the A12 lettering, which would otherwise be baked into the artwork in one language and one color for ever.

Work one layer at a time. In Inkscape's Layers dialog (Shift+Ctrl+L) each layer has an eye to hide it, a padlock to make it ignore the mouse, and an opacity slider. That is how the two halves of the trip stay out of each other's way: lock Places and Place numbers while you drag the area shapes, so only the shapes answer the mouse; hide or fade Areas while you move seats, so the faint fill is not in your way. Downloading only the half you are arranging (above) does the same thing more bluntly, and gives you a far smaller file.

You cannot lose work by drawing into the wrong layer either: the import removes its own markers, its own place numbers and its own area shapes and names, then keeps any layer that still holds something of yours. The colors are a stylesheet at the root of the document, matching each marker by its own class rather than through the layer, so they survive being grouped, ungrouped and moved from one layer to another.

4. Upload it twice. In The arranged layout, choose the file and press Upload the layout with every Apply box left off. That is a dry run: the page reports what would move, what it could not read and what it would leave alone, and writes nothing. Read the report, then tick what you want and upload the same file again.

There are two boxes, one per half of the hall: Apply the places it carries and Apply the area outlines it carries. Tick only the half you worked on. That is not bookkeeping: an afternoon spent dragging seats is an afternoon in which a shape can be nudged without anybody noticing, and the report names every part the file would reshape whether or not you let it. The half you leave alone is left exactly as it is, and applying the places is also what replaces the background, since the picture is of the room.

There is a third box, Also apply the grade each marker names, off by default and deliberately separate. A layout is not where a grade is authored, so a file carries the grades its places had on the day it was downloaded; applying those as a side effect of moving a hall would undo whatever was regraded in Drupal since. Two rules keep it safe: a marker naming no grade says nothing rather than clearing one, and a grade this venue does not have is reported rather than guessed at. To reassign a band this way, edit data-yyk-grade in the editor's XML view.

5. Check it. Open a slot's picker page for a resource in this venue and look at the hall. Then read the Numbering tab, which is derived from the coordinates you have just written and is the thing most likely to disagree with them.

What the markers are, and what you may do to them

  • Each marker is a <use> element whose id is the letter p and the place id. Its natural key - section, subsection, row and place - travels in a data-yyk-place attribute, which is what a report line names it by and what lets a layout built on another site still be read here.
  • Move, turn, scale and group them freely. The position is read through the whole chain of transforms above a marker, not from its own x attribute, so a marker moved by the group it is in is read where it appears.
  • Do not convert one to a path. That folds its rotation into its outline and leaves nothing to read: the upload reports it and leaves that place alone.
  • A marker replaced by a plain circle, ellipse or rect is still read, at the center of the shape.
  • The markers may end up outside the layer they arrived in. Taking a layer apart is one click, and every marker is found wherever it is.
  • An accessible place is drawn as a ringed chair, so set that flag on the place before you download if you want to see which ones they are while you arrange.
  • Section and row labels are drawn only for the places being parked. A second download therefore carries none: yours are already in the artwork, wherever you dragged them, and drawing a fresh set over the top would throw that away every time you opened the file. Their absence is the feature working.

Save as plain Inkscape SVG, not the compressed variant.

Giving a section its angles

angle turns the chair. A place at 0 faces down the page, which is where the bundled glyph points, so a room whose stage is at the bottom of the picture needs no angles at all. Only a curved or a facing section does.

There is no tool here for it, and none is needed, because the editor already has one. In Inkscape, select the section and open Object › Transform › Rotate:

  • With Apply to each object separately ticked, every selected marker turns about its own center and none of them moves. That is how a whole section is given one angle: select the choir, set 180, apply.
  • Without that box ticked, the selection turns as one body about one center, which moves the markers as well as turning them. That is how a straight section is swept into a balcony arc: the geometry and the facing come out together.

Both are read back off the file, so you can mix them freely and check the result in a dry-run upload before writing anything. A place whose marker you never touched keeps the angle it had.

The bundled Auditorium de Bordeaux is the scale to expect for a curved house: 1,422 places carrying 247 distinct angles, because every side section faces the stage from a slightly different bearing. That is a handful of selections, not 1,422 rotations.

What the upload reports

It reports before it writes. Leave the Apply boxes off and it says what would change without changing anything.

It refuses the whole file, rather than applying half of it, when two markers claim one place - which is what copying a marker instead of moving it looks like - when the file carries no marker at all, when none of its markers names a place of this venue, or when the file carries a script or an event handler.

It states, and does not act on, a marker naming a place this venue does not have, a marker whose center cannot be read, a place landing outside the viewBox, and a place with no marker in the file, which keeps the coordinates it has. That last one is what makes a big hall arrangeable one section at a time: narrow the download to a section, arrange it, upload it, and the rest of the house is untouched.

After an upload

position and gap_before are derived from the coordinates, and neither is touched by the upload. Run drush yoyaku:venue-positions <venue> --fix --breaks, or read the venue's Numbering tab, which draws each row threaded in position order. See seating for why they are a separate, deliberate step.

For a hall that already exists elsewhere

If the architect's file or a ticketing predecessor already has one element per place, its coordinates are your coordinates: write a throwaway script that emits the place rows and load them as a venue package (section 4). The bundled Auditorium de Bordeaux arrived that way, with 1,422 places across 17 sections and 247 distinct rotation angles, because its rows curve and each place faces the stage. Nothing about a package and the layout trip conflicts; the package is the faster route when the geometry is already written down somewhere.

What x and y mean exactly

The center of the seat, in the artwork's own coordinate space. The map says so itself: it positions each seat glyph at left: x; top: y and then pulls it back half its own width and height, so the point you give is the middle of the seat rather than a corner of its box.

Two consequences worth knowing before you generate a hall:

  • The distance between two seats of a row is center-to-center, which is what a hall calls its seat pitch. Anything measuring spacing measures that: the aisle detection in seating compares each step against the row's own median step, so a hall drawn at any scale behaves the same.
  • A seat's box never enters into it. Sprites are drawn at a fixed size in the viewport and scale with the zoom, so a wider seat does not move its neighbor: only the coordinate does.

There is no unit. The numbers are whatever the artwork's viewBox uses, and they only ever get compared with each other.

A worked example, small enough to read

A single section, two rows of three seats, on a small artwork:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
  <defs>
    <symbol id="place-glyph" viewBox="0 0 32 32">
      <circle cx="16" cy="16" r="14"></circle>
    </symbol>
  </defs>
  <rect x="150" y="20" width="100" height="24" fill="#ddd"></rect>
  <text x="200" y="37" text-anchor="middle" font-size="14">Stage</text>
</svg>

Row A sits at y: 200, row B behind it at y: 240, three seats each at x: 160, 200, 240, all facing the stage so angle: 0:

{
  "places": [
    { "label": "A1", "section": "orchestra", "grade": "cat1", "row": "A",
      "place": "1", "position": 1, "x": 160, "y": 200, "angle": 0,
      "accessible": false },
    { "label": "A2", "section": "orchestra", "grade": "cat1", "row": "A",
      "place": "2", "position": 2, "x": 200, "y": 200, "angle": 0,
      "accessible": false },
    { "label": "A3", "section": "orchestra", "grade": "cat1", "row": "A",
      "place": "3", "position": 3, "x": 240, "y": 200, "angle": 0,
      "accessible": false },
    { "label": "B1", "section": "orchestra", "grade": "cat2", "row": "B",
      "place": "1", "position": 1, "x": 160, "y": 240, "angle": 0,
      "accessible": false }
  ]
}

Note what each field is for, because three of them look alike:

  • row and place are what is printed on the ticket and read out to a screen reader: row A, place 2.
  • position is the physical order along the row. It drives adjacency and keyboard navigation, so two seats either side of an aisle can be places 10 and 11 while their positions stay consecutive. What says they are nevertheless not side by side is gap_before on the second of them.
  • label is the printed identity as one string, A2, carried for readability.

angle rotates the sprite in degrees. A seat in a curved row faces the stage, so its angle usually equals its bearing from the center of the arc.

3. The entities, and what each one decides

  • Venue (yoyaku_venue) owns the artwork, the two sprite ids and the layout. Every resource running in the hall points at it, so the layout is defined once.
  • Place grade (yoyaku_grade) is a band: a color for the legend and the plan, a label, a weight for ordering. Prices attach per slot, not here. Its label is read by bookers, not only by staff: a tariff names the grades it prices, and the booking page prints those names on the offer so a visitor choosing between two prices can see which part of the house each one books. Name a grade for the person booking the ticket.
  • Section (yoyaku_section) is a named area. Its weight orders the selection panel, its subsection names and area outlines say what each part of it is called and where that part is, one per line, and its default mode says how it is normally booked: placed, assigned or pooled.
  • Place (yoyaku_place) is one physical seat, as above.
  • Configuration (yoyaku_configuration) is a way of opening the hall for a production: which areas are bookable and how. So one venue can run at full capacity for one concert and reduced capacity for the next, with no second venue and no duplicated places.

A configuration holds one row per area it has an opinion about, and that row carries the mode (placed, assigned, pooled or closed), plus a pool capacity and a pool grade when the area is pooled. A configuration that says nothing about an area leaves it at the section's default mode.

An assigned area is offered as a section like a pooled one and drawn like a placed one: its seats are on the plan, dim and not clickable, the section is what takes them, and the seats the booker is given paint as theirs. Its capacity is its places, so it declares none. Which drawn areas those are travels with the map as one short list, grouped under the section that offers them, rather than as a flag on every chair.

How the offers are grouped for bookers

A tariff (yoyaku_resource_tariff) is what carries a price, and it names the grades it prices, so one tariff can merge grades a production does not want to distinguish. Halls usually price the same grades several times over for different audiences (full price, reduced, subscription), which means several tariffs that differ only in who may book them.

A tariff class (yoyaku_tariff_class) is the heading over those. It prices nothing; it has a translatable label a visitor reads and a weight setting the order the audiences are offered in. The slot booking page gives each class a collapsible panel, with one open, so the booker picks an audience before a price.

Classes are optional and always were. A tariff naming no class is offered on its own, and a resource that defines none is offered as a plain list, exactly as it was before classes existed. Add them when a hall has enough tariffs that a flat stack of quantity fields stops being readable.

Subsections, for part of an area

A place can carry a subsection number, and a configuration row can name one. That is how half a balcony is closed while the other half stays bookable: give the places of each half a different subsection, then let the configuration speak about one of them. A configuration row with no subsection applies to the whole section.

Note

A closed area draws as bare artwork: no seats, no outline, no label saying it is closed. That is deliberate. An operator running at reduced capacity is managing weak demand, and the plan does not advertise it.

4. Loading a venue

Everything above can be entered by hand at Administration › Booking › Venues, which is reasonable for a small room and unreasonable for 1,422 seats. For anything real, use a venue package: a directory holding venue.json and the SVG it names.

drush yoyaku:venue-import path/to/my-venue
drush yoyaku:venue-export <venue_id> path/to/write

The yoyaku_placement_io README specifies the manifest field by field, and the bundled examples/auditorium-de-bordeaux is a complete, real package worth reading before you write your own: a 1,422-seat hall with the outlines of its pooled areas, two ways of opening it, and a pinning holding 231 of its seats.

What the package carries

The package is the hall and what belongs to it. It carries the venue label, sprite ids and the artwork file, the grades with their colors, the sections with their labels and weights, every place with its geometry, including the two requirements that decide which seats are adjacent (its position and its gap_before), the configurations that say how the areas are opened, and the pinnings with the places each one holds.

The position and the break are taken as written. The import does not read the coordinates to check them, so a package whose numbering contradicts its own geometry loads exactly as the file says. Reading a hall out of its coordinates is drush yoyaku:venue-positions <venue> --fix, and only when asked. See seating for why.

The one thing it names that is not the venue's is an allotment: a pinned place is held under one, and an allotment is tenant-level, because the same schools take units in every venue a tenant runs. So the package carries the allotments its pinnings need and no more, and an import matches each in the tenant by its key, creating only a key the tenant has never had.

A house that wants less says so. Both commands take --carry: all, the default, none for the layout on its own, or a list of configurations and pinnings. The hall travels whatever it says, and the export reports what it left out.

5. Checking the result

Open a slot's picker page for a resource in the venue. A correct plan draws the artwork, every open seat on top of it in its grade color, a legend, and a selection panel.

The view opens on the seats, since that is what a booker came to choose from, and zooms out to the whole picture. So a stage drawn well below the front row is not visible until somebody zooms out, which is deliberate: the opening view is the part of the hall that can be clicked.

The slot booking page (the quantity page, for a hall booked without a plan) is worth a look too: each offer should name its tariff, print the grades it covers, and show its price next to a minus/value/plus stepper at the end of the row. Every card's price should end at the same place, including a card that carries a limit per order, which states it on its own line under the pair. If the grade names read like internal codes there, that is the venue's grade labels showing through to bookers.

A click holds straight away, with no submit pressed: the page sends the whole selection a few hundred milliseconds after the last click, and the answer carries only what the server owns, which is each offer's availability line, the quantity it holds for this visitor, and the ceiling with the constraints that set it. Constraints travel with the ceiling rather than after it, so a stepper that stops is never explained by a rule that is no longer the one stopping it. Where the booker has chosen a part of the house, the line under the stepper says what that area has left, or that it has nothing left, and the one they have run into is the one marked. Nothing on the page is rebuilt, so a click made while a request is still out is kept and sent by that request's answer instead of being overwritten by it, and an open panel and the keyboard focus stay where they were. A quantity typed into a field is held the same way, once the field is left.

When it looks wrong, the cause is usually one of these:

  • Seats are invisible, though the plan draws. The venue's sprite id names a symbol the artwork does not define. Check the id in the file matches the field, hash excluded.
  • Something drawn in the editor is not on the page. Zoom out. The view opens on the seats, so a stage or an outline drawn outside them is there but not yet in frame.
  • A seat cannot be moved in the editor without taking a group apart. That is a layout from before the layers were layers. Download it again.
  • Seats are bunched in a corner, or off the picture. The coordinates are not in the artwork's viewBox space. Compare the largest x and y against the viewBox width and height. After a layout upload, the Layout tab has already said which places landed outside the box.
  • Every place is drawn twice, one of them with a number on it. The artwork still holds a layer of markers. It is not one this module stored: an upload takes every marker out of the picture it keeps. Re-download the layout, and upload it again without adding markers of your own.
  • A place will not move, however it is dragged. Its marker was converted to a path, so neither its center nor its angle is in the file any more. The Layout tab lists it. Undo the conversion, or re-download and move the fresh marker.
  • A quantity settles on a lower number than was clicked. Not a lost click: the server held fewer places than were asked for, and the message under that control says so. Someone else took them first.
  • A pooled area has no steppers in the panel. It has an outline, so it is being booked from the plan instead. That is the intended behavior: an area with an outline is clicked on the plan, and an area without one gets panel controls.
  • A pooled area is meant to be clickable but is not. No part of its section carries an outline yet. Arrange the Areas layer of the layout, or check the section's own Area outlines field: a shape that could not be read is named in the report of the upload that could not read it.
  • The seats of an area are drawn but cannot be clicked. The area is assigned: the house gives its places out, so the section is what takes them and a click on one of its seats falls through to the area's outline. The seats the booker is given paint as theirs, which is why they are drawn at all.
  • No plan at all, just the panel. Either the venue has no artwork, or no place in an open area has coordinates. A venue offering only pooled areas is expected to look like this and works fine.
  • "The seating plan could not be loaded." The artwork itself could not be fetched. Check the file still exists behind the venue's file field.

6. Replacing the artwork later

Uploading a new file through the venue form is safe: Drupal renames on collision, so the new artwork is a new file with a new URL, and the next page view uses it.

Uploading a layout replaces the artwork in place, keeping the same path, and so does re-importing a package. The picker handles this by putting the file's modification time in the URL it asks for, so a replaced picture is fetched again rather than served from a browser cache.

Those are the three supported ways. Writing over the file outside Drupal leaves the file entity describing something that is no longer there, with a stale size on record, so do not build a deployment around it even where the picture itself happens to refresh.

7. What a rush costs

A booking opening is thousands of people asking one slot the same question. The payload is built so that every answer but the first comes out of a cache, and it splits by how long each part stays true.

The map (the plan, the seats, where they sit) is the same for every production in the hall and every visitor. It has its own URL and its own permanent cache entry, invalidated when the inventory changes.

What only an operator can change - which areas are pooled and what they are called, which tariffs may book what, what each costs, what is added to that price and how much of each one booking may take - is held until they change it. It is keyed per slot, which is what makes the prices safe to hold: a price is resolved per slot, and an entry shared between them would show one evening's price on another evening's map. It is keyed per front as well, because what a booker is told about an offer is the front's to decide: a mini site adding a booking fee says so beside its own prices and not beside the main site's. The allowance is here for the same reason it can be here at all: it is a rule about everybody. How much of it one booker has left is worked out in the browser and never travels.

What a booking moves - which seats are gone, which are held for another allotment, how many units each pooled area has left - is held for yoyaku.settings.availability_max_age seconds. A lifetime rather than a tag, and the difference matters: a rush is readers and writers on one slot, so invalidating on every booking would empty the cache exactly when it is needed. A lifetime bounds the work instead, at one rebuild per slot per lifetime however hard it is asked.

What belongs to one basket - the visitor's own seats, and the colors an operator sees on places another allotment holds - is never shared at all.

Nothing is risked by the staleness. A seat map is already out of date the moment it reaches the screen, since somebody books a second later and the page sits there for minutes; the few seconds the server adds are small against that. Correctness never came from the map anyway - a hold re-checks capacity under the slot lock, so a stale display cannot overbook.

What keeps the staleness from being felt is that a slot's own knowledge corrects the shared answer, and nobody else's does. There are three pieces of it, and between them they cover every way one booker can know better than the list does.

Its basket is read live and wins outright, so a booker who takes a seat and reloads sees it as theirs rather than watching their own booking come back as an empty seat.

A seat it has just given back is subtracted from the list, because releasing leaves nothing behind to read. Without that the seat comes back drawn as somebody else's, and a seat drawn that way cannot be clicked at all, so the booker would be refused their own seat by their own screen until the lifetime lapsed.

A seat it has just been refused is added to the list, because the refusal is the freshest thing anybody knows about that seat: the engine has looked under the slot lock and found somebody else holding it, while the list is older than that and does not mention it. Without this the seat goes on being offered to the one booker who has already been told they cannot have it.

Those notes are pruned as soon as they outlive the lifetime they correct, so they hold no more than what one slot did in the last few seconds, and the two sides are mutually exclusive: the newer requirement about a seat replaces the older one, so the record never holds two opinions of one seat.

Nothing shared is thrown away for any of this, and a refusal is no exception. Dropping the slot's entry on a refusal would cost one rebuild per refusal exactly when refusals cluster, and under contention the lifetime would stop bounding the work at all, which is the one thing it exists to do. The price is that a seat somebody else has taken goes on being offered to every other reader until the lifetime lapses, one refusal each rather than one map caught up at once.

A refused click still asks for the state again, since the answer now carries the correction; turn that off with yoyaku_placement.settings.resync_map_on_refusal if you would rather spend one request less per refusal.