App rescue: fix what was built in a hurry
A barbershop booking site, built fast with React, a small server and SQLite. It looked fine and had six bugs that cost money. For each one: a test that fails, the fix, the test passing.
The barbershop, its customers and the costs below are made up. The tests, the fixes and the run in the video are real.
The six bugs
Costs are rough guesses.
| Bug | What it costs | The fix |
|---|---|---|
| Two people could book the same chair Both got a text. One turns up to a taken chair. | A lost customer and a bad review. | The database allows one booking per slot. |
| Anyone could read every customer's name and phone The staff key shipped inside the site's own JavaScript. | The worst one: a data leak. | A staff password checked on the server. |
| The day opened at 2:00 AM online The server runs in UTC. Afternoons never showed. | Half the day's bookings. | Hours in the salon's own time zone. |
| Anyone could cancel anyone's booking Cancel only needed the booking number. | Empty chairs, no warning. | A private cancel code per booking. |
| The Book button was off the screen on phones Widths fixed for a laptop. | Most people book on a phone. | A layout that wraps and stacks. |
| It said "booked" when nothing was saved A phone typed "(555) 014-2231" was refused, quietly. | People turn up with no booking. | Check the answer, show the error. |
Before and after
The booking page on a 390 px phone.


The same day on a laptop, server in UTC.


Someone else took the slot while the form was open.


The tests
Written before the fixes, run on both copies. Before: 0 of 6 pass. After: 6 of 6.
| Test | Before | After |
|---|---|---|
| Same slot, same moment | fail responses 200 and 200; bookings saved for that slot: 2 | pass responses 200 and 409; bookings saved for that slot: 1 |
| Staff list stays private | fail the staff list opened with "demo-admin-key-not-real", a string read from the site's JavaScript | pass 29 tries with every key-like string in 1 bundle(s), and none: all refused |
| Opens 9:00 AM salon time | fail first slot 2:00 AM, last 9:30 AM, on 2026-10-14 in salon time (want 9:00 AM to 4:30 PM on 2026-10-14) | pass first slot 9:00 AM, last 4:30 PM, on 2026-10-14 in salon time (want 9:00 AM to 4:30 PM on 2026-10-14) |
| Can't cancel others | fail Jen tried to cancel Tom's booking (id 4): 200; Tom's booking is gone | pass Jen tried to cancel Tom's booking (id 3): 404; Tom's booking is still there |
| Fits a 390 px phone | fail page 784 px wide on a 390 px screen; Book button's right edge at 697 px | pass page 390 px wide on a 390 px screen; Book button's right edge at 374 px |
| Honest form | fail "(555) 014-2231": page said "booked", bookings saved 0; a slot taken while the form was open: page said "booked", no error shown | pass "(555) 014-2231": page said "booked", bookings saved 1; a slot taken while the form was open: page said not booked and showed an error |
What changed
161 lines added, 70 removed, 5 files. Same screens and API.
| File | Added | Removed |
|---|---|---|
server.js | +45 | -11 |
slots.js | +20 | -6 |
src/App.jsx | +49 | -34 |
src/Admin.jsx | +37 | -12 |
src/style.css | +10 | -7 |