Skip to Content
Living documentation — last reviewed 2026-05-28
DecisionsADR-0025: A trial may hold a class seat; its outcome stays rep-confirmed

ADR-0025: A trial may hold a class seat; its outcome stays rep-confirmed

Status: Accepted. Amends ADR-0023 (“Trials … do not reserve a seat in a class”).

Context

ADR-0023 made trials recorded appointments with explicit outcomes and no class seat. In practice a studio’s trial is a class: the lead walks into a scheduled session. With no seat, a full class could take a trialist the coach did not expect, members could book the trialist’s spot, and the coach had no way to know who was new. Coaches have no Sales access, so they could not see the trial at all, and the rep had nothing but their own memory to confirm whether the lead showed up.

Bookings already have a hard capacity (sessionOccupancy, serializable booking transaction), a waitlist engine (fillFreeSeats) and an auto no-show job for members.

Decision

  1. A trial may sit in a class session. lead_trial_appointments.class_session_id links it. While the appointment is pending or attended and not superseded it holds one seat of that session, mirroring confirmed and attended bookings, counted inside sessionOccupancy, so every capacity decision and every displayed count includes it. Confirming a trial as attended mid-class does not hand its seat to the waitlist. A trial without a session (a tour, an intro meeting) keeps a free time and holds nothing.
  2. The claim is race-safe the way a booking is. It runs in the Sales result’s serializable transaction and locks the session row; a full class is 409 trial_session_full.
  3. Ending a trial that did not happen frees the seat and refills the waitlist after commit (trigger trial_released): reschedule, Not relevant, archive, and a rep’s no-show or cancelled outcome. When one of the lead paths ends a trial the coach had checked the lead into, the trial happened: it ends attended, is not superseded and keeps its seat. Cancelling, deleting or overwriting the class (bulk delete, a template apply with overwrite, an Arbox import cancelling it) ends its trials by the same rule, records a sales_trial activity (session_cancelled) on each lead, closes the rep’s trial task for the cancelled ones and runs no waitlist. A class holding only trials is not empty: the “empty classes only” deletes and the template overwrite leave it alone. A session time change moves the trial with it.
  4. Converting the lead before the class moves the seat into a member booking. Every conversion or member-linking path ends a pending, not checked-in trial in a class that has not started as converted and gives the member a confirmed booking in that class (bookings.trial_appointment_id), in the conversion’s own transaction, with no capacity check and no waitlist run. A plan pays for it when exactly one of the member’s plans could pay for the class as a fresh booking; otherwise it carries none, and the entitlement sweep never revokes it. That booking is still the coach’s trial until the member cancels it: booking the class again is a plain booking. Any other pending trial (a class that already started, a tour) ends cancelled in the same transaction on every path.
  5. A trial’s outcome stays rep-confirmed. A coach’s check-in on a trial seat is recorded as evidence (checked_in_at, checked_in_by_user_id, a lead activity) and leaves the outcome pending; the seat stays held. The rep confirms attended / no-show / cancelled in the Sales queue, with attended preselected after a check-in. The automatic outcomes are attended for a checked-in trial that another path closes, because that path would otherwise record a trial that happened as cancelled, and converted when the seat moves into a booking. The auto no-show job never touches trial appointments.
  6. Coaches are told about trials, nothing else. A roster entry is a trial only when someone said so: Sales scheduled it, a staff member marked that booking, or the booking is paid with a plan the owner flagged is_trial, in that precedence. The reason stays in the API (it decides who may unmark): no client shows a coach why someone is a trial, only that they are, with canMarkTrial / canUnmarkTrial telling the client which action to offer. There is no derived “first class” / “new” / “returning” signal (owner decision): the coach acts on who someone asked them to look after, not on attendance counts.
  7. Coaches see the trialist’s intake note on the roster, without Sales access. The note often says why the person came, which the coach needs at the door; the rest of the Sales record (lead source included) stays behind Sales access, and members never see the trial columns on their own bookings.
  8. No feature flag. Seat logic only engages when a rep picks a session, plan trials only when an owner flags a plan; the coach surfaces are additive.

Why no auto no-show for trials

  • A trial’s outcome is a sales fact with consequences (follow-up path, conversion, automation journeys on no-show), owned by the rep. ADR-0023 already holds that elapsed time is not proof of a no-show.
  • Coach check-in is not reliable enough to be the only signal: coaches forget to tap, trialists arrive late, a busy class is checked in after the fact. A missing check-in turned into an automatic no-show would trigger the wrong follow-up for a lead who attended.
  • The rep confirming with the check-in as a hint costs one tap and keeps a single owner for the outcome.

Consequences

  • Members see one fewer seat per trial. Seat counts on every surface match what book() enforces.
  • Trial lifecycle writes in Sales and lead archive now touch scheduling (waitlist refill); those paths must keep calling endPendingTrials and refilling after commit. Every conversion path must call TrialSeatsService.moveIntoBooking before ending the rest. Class cancel/delete paths must keep calling endSessionTrials inside their transaction.
  • A trial whose outcome is never confirmed keeps its seat until the session starts and then stays as outcome work; nothing cleans it up automatically.
  • A lead whose class was cancelled is left without a next action; the rep rebooks from the queue. The lead is not notified.
  • Coaches gain read access to trialists’ names, phone numbers, intake notes and staff notes through the roster, without Sales access.

Alternatives considered

  • Create a booking for the trialist when the trial is scheduled. Rejected: a lead has no membership or user, and a booking pulls in credits, entitlements, eligibility and member notifications that do not apply. The booking is created only once the lead converts and has a membership.
  • Derive who is new from attendance history (a member’s first class, a new or a returning member). Rejected by the owner: an attendance-derived label is a guess the coach did not ask for, and an explicit staff mark or trial plan covers a walk-in trial that never went through Sales.
  • Keep trials seat-less and show them on the roster only. Rejected: the class could still overfill, which is the main operational complaint.
  • Auto no-show after the session when not checked in. Rejected for the reasons above.
  • Coach check-in sets attended. Rejected: it would bypass the rep’s outcome flow (next step, conversion) and split outcome ownership.