Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesTrialsTrials — Code Map

Trials — Code Map

API

Module — apps/api/src/trials/

FilePurpose
trials.module.tsTrialsModule: roster, notes, brief, time-change listener, member trial bookings and their controllers; imports FormsModule (health declaration), BookingsModule (the booking engine) and TrialSeatsModule (the class picker). Imported by ClassSessionsModule, which hosts the session-scoped routes.
trial-seats.module.tsTrialSeatsModule: TrialSeatsService alone, imported by SalesWorkspaceModule and OrganizationLeadsModule. Kept out of TrialsModule so Sales does not pull in Forms: Sales → Trials → Forms → Notifications → … → Sales would be a module cycle.
session-roster.service.tsSessionRosterService: roster (API-internal TrialSource per entry: lead > staff > plan; canMarkTrial / canUnmarkTrial), markBookingTrial (409 trial_not_markable for a Sales or plan trial and for cancelled / no-show bookings), checkInTrial / undoTrialCheckIn (evidence + sales_trial activity), createTrialNote, trialsToday, trialsFor (shared with the brief and the Today rows).
staff-notes.service.tsStaffNotesService: staff check (requireStaff), member and lead note CRUD, notesForSubjects (batched, with converted-from lead notes), canEdit.
staff-notes.controller.tsorganizations/:orgId/members/:membershipId/notes and organizations/:orgId/sales-workspace/leads/:leadId/notes.
member-trial-bookings.service.tsMemberTrialBookingsService: book (marks a live booking under its row lock, else BookingsService.bookForMember with trialMarkedByUserId), list (upcoming trials for any reason, canUnmark), sessionOptions (listTrialSessions).
member-trial-bookings.controller.tsorganizations/:orgId/members/:membershipId/trial-bookings — GET, POST, GET session-options.
trial-seats.service.tsTrialSeatsService: listTrialSessions (Sales picker), claimSeat (row lock + capacity), release (waitlist refill, trigger trial_released), moveIntoBooking (a converting lead’s seat becomes their booking, plan picked and charged in a savepoint; a revived row drops any stale staff mark).
trial-session-sync.listener.tsTrialSessionSyncListener on SESSION_TIME_CHANGED: moves pending trials, the lead’s trial_date and the follow_up_trial task.
trial-brief.service.tsTrialBriefService.sendDueBriefs — @ScheduledJob every 5 min; due query joins the coach’s membership in the session’s org; marker claim, push enqueue, re-arm only if the enqueue throws.
trials.driver.tsIntegration driver for the specs below (session-roster, trial-seats, trial-brief, staff-notes, member-trial-bookings).

Seats — apps/api/src/bookings/

FilePurpose
trial-seats.tsSEAT_HOLDING_TRIAL_OUTCOMES, holdsTrialSeat, trialSeatCounts, endPendingTrials (a lead’s trials), endSessionTrials (a cancelled/deleted/overwritten class’s trials, with sales_trial activity and task close).
booking-enforcement.util.tssessionOccupancy / seatsTaken include trial seats; promotionVerdict judges a seat taken now (waitlist promotion, a moved trial seat).
booking-entitlement-sweep.service.tsloadCandidates skips bookings with trial_appointment_id.
booking-events.tsPromotion trigger trial_released.
bookings.service.tsbookForMember / bookForSubject take BookingWriteOptions.trialMarkedByUserId, written with the booking (insert or revive; a revive resets trial_appointment_id and any old mark). Member-facing responses (bookForSubject, getBookingsForUser) omit both trial columns.

Callers elsewhere

FileWhat
class-sessions/class-sessions.controller.tsGET trials-today, GET :id/roster, PUT :id/bookings/:bookingId/trial, POST|DELETE :id/trials/:appointmentId/check-in, POST :id/trials/:appointmentId/notes.
class-sessions/class-sessions.service.tsgetBookingCounts and findUpcomingToday add trial seats; findUpcomingToday rows carry trialCount; resolveBulkFilter counts trial seats as bookings; cancel, deleteSession, bulkDelete call endSessionTrials.
sales-workspace/sales-workspace.controller.tsGET trial-sessions.
sales-workspace/sales-workspace.service.tsscheduleTrialInTx (end pending trials + claim), refill after a no_show/cancelled outcome, Not relevant; closeAcquisitionAsConverted moves the seat (moveIntoBooking) then ends the rest; lead detail classSession / checkedInAt / checkedInBy, convertedMember.
organization-leads/organization-leads.service.tsArchive and bulk archive end pending trials (endPendingTrials) and refill freed seats; convertLead and attachLeadToMembership (CRM link, self-signup) run the conversion writes and closeTrialsOnConversion (moveIntoBooking, then endPendingTrials for the rest) in one transaction, then release.
program-templates/schedule-planner.ts, program-templates/program-templates.service.tsloadExistingSessions counts trial seats as bookings (overwrite blocks such a class); applyToSchedule calls endSessionTrials on the sessions it replaces.
arbox-bridge/arbox-schedule-import.service.tscancelDisappeared calls endSessionTrials on the classes it cancels.
plans/plans.service.ts, plans/plans.controller.ts, plans/dto/*, subscriptions/subscriptions.controller.tsisTrial, trialOncePerPerson and the trial expiresInDays on create, update and every plan response; update refuses un-flagging a sold trial (400 trial_plan_in_use) and resets trialOncePerPerson on un-flag; purchase calls assertTrialNotUsed for non-staff actors.
plans/trial-once-per-person.util.tsassertTrialNotUsed — the once-per-person rule (409 trial_already_used; held, or a pending checkout of another trial plan).
subscriptions/subscriptions.service.ts, payments/services/{webhook-processing.service,billing-settlement,recurring-charge.service,cash-plan-coverage.service,manual-payment.service}.tsStamp the trial’s use-within window on activation via consumableValidityEnd.
forms/forms.service.tshealthDeclarationStates.
push-notifications/notification-copy.tspushCopy.trialBrief.
users/dto/update-notification-prefs.dto.tsAccepts trials.

DB — libs/db

  • src/lib/schema/staff-notes.ts — staffNotes.
  • src/lib/schema/sales-workspace.ts — leadTrialAppointments.classSessionId, checkedInAt, checkedInByUserId.
  • src/lib/schema/scheduling.ts — classSessions.trialBriefSentFor, bookings.trialMarkedByUserId, bookings.trialAppointmentId.
  • src/lib/schema/payments.ts — plans.isTrial, plans.trialOncePerPerson.
  • drizzle/0184_trials.sql.

Shared — libs/shared

  • src/lib/schemas/trials.schema.ts — notes, roster (rosterTrialSchema = { lead }, canMarkTrial, canUnmarkTrial), mark-trial body, trials today, trial-session option, member trial booking (createMemberTrialBookingSchema, memberTrialBookingSchema). No trial source: it is API-internal.
  • src/lib/schemas/plan.schema.ts — isTrial, trialOncePerPerson, isExpiryAllowedForPlan, consumableValidityEnd, planKind, TRIAL_REQUIRES_DROP_IN, TRIAL_PLAN_IN_USE.
  • src/lib/constants/payment-error-codes.ts — TRIAL_ALREADY_USED.
  • src/lib/schemas/sales-workspace.schema.ts — trial.classSessionId.
  • src/lib/schemas/notification-prefs.ts — trials.
  • src/lib/i18n/dictionaries/{en,he,ru}.json — trials (incl. trials.planSheet, trials.alreadyUsed, trials.memberTrials), trialsPush, pushNotifications.categories.trials, plans.types.trial, plans.dialog.typeHints.trial.

Web — apps/web/src

FileWhat
components/overview/schedule/session-trials.tsxSessionTrials block, TrialBadge, TrialMarkButton (Mark on canMarkTrial, Unmark on canUnmarkTrial).
components/overview/schedule/session-detail-sheet.tsxRoster from GET …/roster; mark / unmark; lead trial check-in / undo; add note.
components/staff-notes/staff-notes.tsxStaffNotes — list, add, important toggle, edit/delete by canEdit.
components/overview/members/member-detail-page.tsxMember notes and the Trial classes card (overview tab).
components/trials/member-trials-card.tsxMemberTrialsCard — a member’s upcoming trials (status, coach), Unmark when canUnmark, “Book trial class”.
components/trials/book-trial-dialog.tsxBookTrialDialog — class picker (…/trial-bookings/session-options), plan picker on plan_selection_required, refusal copy.
components/overview/plans/plan-dialog.tsxTrial workout kind: trial-only fields (price help, Use within, One per person), hidden recurring/pack/presale/group/limit fields.
app/[lang]/(protected)/dashboard/plans/plans-content.tsx, components/overview/plans/plan-detail-page.tsx”Trial workout” type label / header badge (planKind).
app/[lang]/(protected)/(member)/shop/page.tsx, components/member/family/add-child/plan-step.tsx, app/[lang]/join/[token]/join-continue.tsxtrial_already_used → trials.alreadyUsed.
components/overview/v2/today-panel.tsxTrial count chip.
app/[lang]/(protected)/dashboard/leads/sales-workspace.tsxTrial session picker in the result editor.
app/[lang]/(protected)/dashboard/leads/sales-lead-sheet.tsxTrial class/coach/check-in line, converted trials; lead notes; the converted member’s Trial classes card (convertedMember).
app/[lang]/(protected)/dashboard/leads/sales-row-editors.tsx, sales-trial-outcome.tsxOutcome editor preselects attended when checked in.

Mobile — taikan-mobile

FileWhat
src/hooks/use-trials.tsMirrored types; useIsStaff, useTrialsToday, useSessionRoster, useRosterCheckIn, useAddRosterNote, useMarkBookingTrial. In-memory only (persist: false).
src/components/home/trials-today-card.tsxTrialsTodayCard on the home screen.
src/components/schedule/session-roster.tsxRoster section on the session screen.
src/components/schedule/trial-badge.tsxTrialBadge.
src/i18n/trials-strings.ts, src/i18n/use-trials-strings.tshe/en/ru strings.
app/(tabs)/index.tsx, app/(tabs)/schedule/[id].tsxMount the card and the roster (staff only).
src/hooks/use-push-notifications.tsA push for another org the user belongs to (the trials brief) switches the active org before navigating.
app/(tabs)/profile/notifications.tsxHides trials from non-staff.
src/lib/query-keys.tssessions.roster, sessions.trialsToday under sessions.all.