A planetarium show has hard, finite capacity, and two channels sell it at once — the website and the counter POS in the lobby. A visitor buying online and a clerk selling the last row at the same moment must not both succeed. Layer on holiday closures, per-show discounts and cancellation requests, and 'how many seats are actually left' stops being a simple read.
What I did about it
Show capacity is modelled on ShowTime, not Show — a booking consumes a specific dated slot, so the counter and the website contend for one row rather than reconciling two counters afterwards.
Cancellations are a CancellationRequest with its own lifecycle rather than a flag on the booking, so a refund that is requested, approved and then reversed leaves a readable trail instead of an ambiguous final state.
Holidays and a SiteMode switch are first-class tables, so closing the planetarium for a day or putting the site into maintenance is an operator action rather than a deploy.
Tickets are generated server-side with PDFKit and reports with ExcelJS, so what the visitor receives and what finance reconciles come from the same source rather than two renderings of it.
What I rejected, and why
The honest part of this project is the audit. A committed AUDIT_REPORT.md tracks the cleanup: 15 backend runtime bugs fixed, the frontend TypeScript error backlog cut from 96 to 47, 18 debug console.logs removed — two of which were leaking auth tokens — and a dead, backend-less /admin/tickets subtree deleted. next build still passes only because ignoreBuildErrors is set; clearing the last 47 errors so that flag can be removed is the open item, and it is written down rather than glossed over.
next build still passes only because typescript.ignoreBuildErrors is set, with 47 errors left in the frontend backlog. That flag hides the next real type error behind 47 old ones, and clearing them is the open item.