README test count corrected to 1,934 (4 skipped) with the square_webhook_events migration entry; Technical Manual fixed to match the bounded try-lock, terminal flow, till idempotency, and refund sweep behaviour, and records the RespondError de-scope for the payments package; Feature Catalog and P11 plan corrected to match the actual UserBookingModal/CardSelection wiring.
62 KiB
Feature Catalog
A comprehensive catalog of every feature in the Crussell nail salon booking platform. Each feature area includes a layman description, sub-features, and cross-references to related areas. Built from a full codebase audit (Go 1.26.5 backend + SvelteKit 5 SPA + PostgreSQL 17).
Last Updated: July 2026
1. Booking System
Three booking flows for creating appointments, each with its own entry point and reservation TTL. The entire system revolves around slots — 15-minute time windows that are blocked by reservations, bookings, time blockers, holidays, and lunch breaks.
Related: Payments (deposits), Availability & Scheduling (availability), Notifications (booking alerts)
1.1 Self-Service Booking (Customer-Facing)
What it does: Customers book online through a 5-step wizard on the website. Select services, pick a date/time, fill in details, and optionally pay a deposit. The slot is temporarily held (reserved) while they complete the flow so nobody else can take it.
How it works:
- Step 0 (guest only): Welcome — login or continue as guest
- Step 1: Service selection — choose from the catalog; services requiring patch tests are grayed out if ineligible
- Step 2: Date & Time — calendar date picker + 15-min time slot grid; slot reserved on backend
- Step 3: Details — guest fills name/email/phone; authenticated users skip
- Step 4: Payment/Confirmation — if deposit required → card payment form; if not → booking confirmed immediately
Layman summary: "Walk through the online booking form — choose what you want, pick a time, tell us who you are, and you're booked."
Related: Services Catalog, Patch Tests, Deposit System, Slot Reservation System
1.2 Deposit System
What it does: Some customers need to pay a 20% deposit upfront to secure a booking (enforced after accumulating no-shows). The deposit must be paid 24 hours before the appointment, or the slot becomes vulnerable to eviction.
Key rules:
- Deposit required = 20% of booking total
- Deadline = 24 hours before start time
- If unpaid by deadline → booking enters
pending_release(slot vulnerable) - If another booking claims the slot → evicted to
deposit_lapsed - Paying ≥20% at any point promotes back to
confirmed
Layman summary: "A security deposit system. Some bookings need 20% up front. If you don't pay in time, someone else can take your slot."
Related: Payments, Booking Statuses & Transition Rules, No-Show Tracking
1.3 Walk-In Booking (Admin)
What it does: Admin creates a booking for a customer who walks in without an appointment. The slot is held for 15 minutes while the customer is at the counter.
Layman summary: "Customer walks in off the street. Staff quickly book them in with a 15-minute hold."
Related: Admin Dashboard, Today Page (Walk-In Wizard)
1.4 Call-In Booking (Admin)
What it does: Admin creates a booking over the phone while the customer is on the line. Same as walk-in but service durations are calculated from the services selected.
Layman summary: "Customer calls up. Staff book them in while on the phone."
Related: Admin Dashboard, Today Page (Call-In Wizard)
1.5 Services Catalog
What it does: The salon's list of services (e.g., "Gel Manicure", "Acrylic Extensions"). Each has a price, duration, minimum age requirement, and optional patch test requirement.
Layman summary: "The price list. Every service has a set duration and may need a patch test."
Related: Custom Services, Patch Tests
1.6 Patch Tests
What it does: Some services (e.g., acrylics) require a patch test 24-48 hours before the appointment. The system tracks which customers have taken which tests and when they expire (6 months).
Layman summary: "You need an allergy test before some treatments. The system remembers when you had it and nags if it's expired."
Related: Services Catalog, Admin Dashboard (Patch Test Management)
1.7 Custom Services
What it does: One-off or special-request services not in the permanent catalog (e.g., "Bridal Party Special"). Admin can create them on the fly and optionally promote them to permanent services later.
Layman summary: "Special requests. If a customer wants something not on the menu, staff can create a custom service just for that booking."
Related: Services Catalog, Admin Dashboard (Services & Custom Services Management)
1.8 Slot Reservation System
What it does: When a customer picks a time in the booking wizard, the slot is temporarily blocked so nobody else can take it. Reservations are stored as time blocker entries with type-specific TTLs.
TTLs by type:
- Logged-in user: 1 hour
- Anonymous user: 10 minutes
- Admin walk-in/call-in: 15 minutes
- Edit requests: 24 hours
Layman summary: "While you're filling out the booking form, that time slot is 'held' so nobody else grabs it."
Related: Availability & Scheduling, Slot Reservation TTLs
1.9 Overlap Prevention
What it does: Ensures no two bookings occupy the same time slot. Uses database-level FOR UPDATE row locks inside transactions. Also checks against time blockers (admin-created blocks, holidays, recurring blocks).
Layman summary: "The system makes sure nobody is double-booked. If a slot is taken, it's taken."
Related: Availability & Scheduling (Time Blockers, Holidays), Time Blockers
1.10 Booking Edit Requests
What it does: Customers can request to change their booking time or services without admin involvement. If no payments exist and the booking is >48 hours away, changes are auto-approved. Otherwise, admin reviews and approves/denies.
Layman summary: "Need to change your appointment? You can request it online. Simple changes happen automatically; complex ones need staff approval."
Related: Admin Dashboard, Notifications (edit_requested), Availability & Scheduling
1.11 Cancellation
What it does: Customers can cancel their own bookings (status goes to client_cancelled). Admin can cancel any booking with optional fee forgiveness and refund processing. Refund amount depends on how much notice was given.
Refund tiers:
-
72 hours: Full refund
- 24-72 hours: Keep up to 50% as deposit
- <24 hours: No refund
Layman summary: "Need to cancel? Do it online. You'll get a full refund if you cancel more than 3 days before."
Related: Payments (Refunds), Notifications, Deposit System
1.12 Idempotency Keys
What it does: Prevents duplicate bookings if the customer accidentally submits the form twice. Each submission has a unique key; if the same key is used again, the existing booking is returned instead of creating a new one.
Layman summary: "If you accidentally click 'Book Now' twice, you won't end up with two appointments."
Related: Double-Payment Prevention, Background Jobs (idempotency key cleanup)
1.13 Booking Statuses & Transition Rules
What it does: Nine booking statuses govern the lifecycle of each appointment. Each has valid transitions enforced by the ProgressBookingHandler.
Statuses: pending (needs admin approval) → confirmed → in_progress → completed (terminal). client_cancelled, we_cancelled, no_show (terminal). pending_release (deposit deadline passed, slot vulnerable) → deposit_lapsed (terminal, slot given away).
Valid transitions:
pending→ confirmed, completed, client_cancelled, we_cancelledconfirmed→ in_progress, completed, client_cancelled, we_cancelledin_progress→ completedpending_release→ pending, confirmed, client_cancelled, we_cancelledno_show,deposit_lapsed— terminal, no transitions
Layman summary: "Each booking has a status that moves forward: waiting approval, confirmed, in progress, done. Some statuses (cancelled, no-show) are final."
Related: Deposit System (pending_release/deposit_lapsed), Today Page (ProgressBooking)
1.14 Slot Reservation TTLs
What it does: When a time slot is "held" during the booking process, it's stored as a special time blocker entry with a type-specific time-to-live.
| Reservation Type | TTL | DB Pattern |
|---|---|---|
| Logged-in user | 1 hour | RESERVATION:user:<userID>:<nanotimestamp> |
| Anonymous user | 10 minutes | RESERVATION:anon:<ipHash>:<nanotimestamp> |
| Admin walk-in | 15 minutes | RESERVATION:admin:walkin:<customerID>:<nanotimestamp> |
| Admin call-in | 15 minutes | RESERVATION:admin:callin:<customerID>:<nanotimestamp> |
| Edit request | 24 hours | RESERVATION:edit_request:<bookingID> |
Anonymous rate cap: max 50 reservations per IP in 10 minutes.
Layman summary: "Different types of holds have different time limits — logged-in users get an hour, walk-in customers get 15 minutes."
Related: Slot Reservation System, Background Jobs (reservation cleanup)
2. Payments
Multi-method payment system accepting Square (card terminal & online), cash, gift cards, and saved cards. Supports deposits, full/partial payments, tips on completed bookings, and refunds with notice-period tiers.
Related: Booking System (deposits), Gift Cards (pay by gift card), Admin Dashboard (till purchases)
2.1 Online Card Payment (Square — saved cards or new cards via Web Payments SDK)
What it does: Customers pay online with a card. Saved-card payments work via Square tokenized card IDs (ccof:); new-card payments are tokenized client-side through the Square Web Payments SDK into cnon: nonces and accepted by the backend everywhere. Saving a card also provisions a Square customer profile (P14), reused for subsequent saves. The backend rejects raw PANs (PCI-DSS parity, mirrored by the dev mock). Local dev can opt into the built-in frontend mock (VITE_SQUARE_ENVIRONMENT=mock), which renders a plain HTML card form and mints the same cnon: tokens the backend dev mock accepts — a full as-if-live walkthrough with zero credentials; without credentials or mock mode, new-card entry is gated behind a CardEntryUnavailable notice. Used for deposits, full payments, balance payments, and tips.
Layman summary: "Pay online with your card — just like any online shop."
Related: Saved Cards, Double-Payment Prevention
2.2 Square Terminal (In-Person Card)
What it does: Admin initiates a card payment on the Square Terminal. Customer taps or inserts their card at the terminal. Admin polls for completion.
Layman summary: "Tap your card on the reader at the counter."
Related: Till Purchases (POS), Infrastructure & DevOps (build tags for dev mock)
2.3 Cash Payment
What it does: Admin records a cash payment. The system calculates change due. Optionally "keep the change as tip."
Layman summary: "Pay with cash. Staff enter the amount; the system tells them how much change to give."
Related: Tips, Till Purchases (POS)
2.4 Gift Card Payment
What it does: Customers pay using a 12-digit gift card code or their account balance (balance from redeemed gift cards). VAT treatment depends on whether the card is SPV or MPV.
Layman summary: "Use a gift card to pay — either enter the code or use your online balance."
Related: Gift Cards, VAT Calculation
2.5 Saved Cards
What it does: Customers can save their card details for faster checkout next time. Cards are tokenized via Square (ccof: card IDs; the full PAN exists only in Square's vault — our DB stores only the reference + brand/last4/fingerprint). Saving a card also provisions a Square customer profile (P14) — square_customer_id is stored on the row and reused for subsequent saves. The dev mock mirrors this (raw PANs rejected). Soft-deleted with 7-year UK retention. The "Add Card" flow posts a card_token (a Web Payments SDK cnon: nonce) to CreatePaymentMethodFromToken, which calls CreateCardOnFile. When frontend Square credentials are unset and mock mode is off (local dev), add-card shows the CardEntryUnavailable notice; with VITE_SQUARE_ENVIRONMENT=mock it uses the frontend mock form instead (saved mock cards appear as ccof:mock_* rows in the dev DB).
Layman summary: "Save your card for next time — one-click payment."
Related: GDPR & Compliance (financial data retention), Frontend Architecture (Cards tab)
2.6 Tips
What it does: Customers can add a tip to a completed booking. Available as percentage presets (10%/15%/20%) or custom amount. Cash tip via "keep change as tip" checkbox.
Layman summary: "Add a tip after your appointment — either by card or by leaving the change."
Related: Booking System (completed bookings trigger tip eligibility), Today Page (daily tips summary)
2.7 Refunds
What it does: Admin processes refunds with automatic routing by payment method. Square card payments are refunded via Square API (post-DB-commit). Cash refunds are credited as gift card balance. Gift card payments are returned to the card's balance. Refunds are resolved by Square's actual status (COMPLETED→completed, PENDING→left pending for the sweep, FAILED/REJECTED→failed) so an in-flight refund never blocks the over-refund guard. A background sweep (sweep-pending-square-refunds) retries/reconciles stuck refunds with a 23h age guard and surfaces failures as admin notifications.
Layman summary: "If a booking is cancelled, the system automatically refunds the right amount to the right place."
Related: Cancellation, Refund to Gift Card, Background Jobs (refund sweep)
2.8 Payment Split Logic (buildSplitRecords)
What it does: When a customer pays online, a single Square charge is automatically split into up to 3 records: deposit portion (first 50%), balance portion (remaining owed), and tip portion (overflow beyond the total). This protects the deposit accounting.
Layman summary: "The system smartly divides your payment — some goes to the deposit, the rest to the balance, and anything extra becomes a tip."
Related: Deposit System, Tips
2.9 Double-Payment Prevention
What it does: Two mechanisms prevent accidental double-charges: PostgreSQL advisory locks (serialize payment attempts per-booking at the database level) and idempotency keys (client-generated unique IDs, or deterministic keys derived from booking+type+amount+card for the admin saved-card path). Same-key retries reuse the original pending record instead of charging again. A background sweep fails stale pending payments older than Square's ~24h key-retention window, so a late retry is cleanly rejected rather than issuing a second charge.
Layman summary: "If you open two tabs and try to pay twice, only one payment goes through."
Related: Idempotency Keys, Idempotent Purchases, Background Jobs (stale-pending sweep)
2.10 VAT Calculation
What it does: Applies VAT to payments and till sales based on business settings (rate, registration status, SPV/MPV voucher type). Discount, on-the-house, and tip payments are exempt.
Layman summary: "VAT is calculated automatically on every payment based on your settings."
Related: VAT Treatment (SPV vs MPV), Business Settings
3. Availability & Scheduling
Manages when the salon is open, handles exceptions (holidays, time off), and computes available time slots for booking.
Related: Booking System (availability calculation), Background Jobs (staged hours, cleanup)
3.1 Default Weekly Hours
What it does: The salon's regular opening hours for each day of the week.
Default schedule (from seed data):
| Day | Hours |
|---|---|
| Mon | Closed |
| Tue | 09:00-17:00 |
| Wed | 09:00-17:00 |
| Thu | 12:00-20:00 |
| Fri | 09:00-17:00 |
| Sat | 09:00-17:00 |
| Sun | Closed |
Layman summary: "The salon's regular opening hours."
Related: Staged Default Hours Changes, Admin Dashboard (Business Settings)
3.2 Exceptional Hours (Holidays & Special Days)
What it does: One-off schedule changes for holidays, early closing, or special openings. Uses a three-table design: group metadata (e.g., "Christmas"), 7-day hours per group, and date-range applications.
Layman summary: "Holiday hours or special openings — like 'Open Sunday 2-5pm for Mother's Day'."
Related: Default Weekly Hours
3.3 Staged Default Hours Changes
What it does: Admin can schedule future changes to opening hours with an effective date picker. Conflict detection checks all active bookings in a 90-day window. Changes auto-apply at midnight via a daily cron job.
Layman summary: "Planning to change your hours next month? Set it up now and it'll switch automatically on the right day."
Related: Background Jobs (apply-default-hours), Notifications (default_hours_changed)
3.4 Time Blockers
What it does: Admin can block specific time slots for any reason (dentist appointment, staff training, personal time). Supports one-off blocks and recurring blocks (cron expressions like "every Tuesday 2-3pm").
Layman summary: "Block out time in the calendar — whether it's a one-off or every week."
Related: Slot Reservation System, Admin Schedule (Week View)
3.5 Available Hours Calculation
What it does: The system computes available time slots by: loading working hours → subtracting existing bookings (with gap logic) → subtracting time blockers → applying lunch protection → applying late-night lock. Self-blocking prevention ensures a user's own reservation doesn't hide the slot from them.
Layman summary: "The system figures out when you're free by looking at your hours, taking out appointments, blocked time, lunch, and late-night restrictions."
Related: Overlap Prevention, Lunch Protection, Late-Night Lock
3.6 Lunch Protection
What it does: Prevents bookings that would overlap the artist's lunch break. Shown as a warning or blocked slot on the time picker. Skipped on short days (≤5 hours).
Layman summary: "Nobody can book over your lunch break."
Related: Available Hours Calculation
3.7 Late-Night Lock
What it does: After 22:00 (10pm UK time), the system blocks the next calendar day's 00:00-11:00 slots from appearing for non-admin users. This prevents customers from booking early-morning slots late at night when the artist may be asleep and unable to handle schedule conflicts. Admin users bypass this lock automatically.
Actual logic: if current London time >= 22:00 → subtract 00:00-11:00 from next day's available slots. Admin can additionally use the out_of_hours flag to book outside normal working hours entirely.
Layman summary: "After 10pm, you can't book tomorrow's morning slots online — the system assumes the salon is closed for the night. If you need a late or early appointment, call the salon."
Related: Available Hours Calculation
3.8 Admin Schedule (Week View)
What it does: Google Calendar-style weekly view showing all appointments, closed days (striped overlay), and time blockers. Accessible from the navigation bar.
Layman summary: "See the whole week at a glance — appointments, closures, and blocked time."
Related: Admin Dashboard, Today Page
4. Gift Cards
Physical and digital gift cards with multi-method purchase, 24-month rolling expiry, balance pooling, and admin recovery of expired balances.
Related: Payments (purchase/redeem), VAT Calculation (SPV/MPV), Admin Dashboard (gift card management)
4.1 Gift Card Types
What it does: Three types of gift cards:
- Standard: Purchased with a balance >0, 24-month rolling expiry
- Inventory: Zero-amount physical cards for stock management, topped up later at the till
- Redeemed: Balance moved to user's pooled account
Layman summary: "Gift cards come in three flavours: bought (has money), blank (needs topping up), or redeemed (money moved to your online account)."
Related: Purchase Methods, 24-Month Rolling Expiry
4.2 Purchase Methods
What it does: Gift cards can be bought four ways:
- Cash — record cash received at the till
- Card Machine — Square Terminal payment
- Online Square — Web Payments SDK (buy from website)
- On the House — giveaway (no payment)
Layman summary: "Buy a gift card with cash, card, online, or as a freebie."
Related: Till Purchases (POS), Online Gift Card Purchase
4.3 Online Gift Card Purchase
What it does: Customers buy gift cards from the website. Payment is processed via Square. If buying for themselves, balance is auto-redeemed. If buying for a friend, a code is generated and the recipient's email is logged for future email delivery (SMTP not yet wired — TODO).
Layman summary: "Buy a gift card online — for yourself (instant) or for a friend (code generated, email delivery pending)."
Related: Online Card Payment (Square Web Payments SDK)
4.4 24-Month Rolling Expiry
What it does: Gift cards expire after 24 months of inactivity. "Last use" includes balance checks, top-ups, redemptions, and payments. Each use resets the timer. Expired balances move to a recovery table.
Layman summary: "Gift cards don't expire if you keep using them. After 2 years without use, the balance moves to a recovery account."
Related: Idle Account Cleanup, Expired Balance Recovery, Background Jobs (cleanup-expired-gift-cards)
4.5 Idle Account Cleanup
What it does: Accounts (gift card holders) with no balance and 2+ years of inactivity are anonymized. Accounts with a balance and 5+ years of inactivity have their balance moved to expired balances, then the account is anonymized.
Layman summary: "Dormant accounts are cleaned up after 2-5 years. Any remaining balance is preserved for recovery."
Related: 24-Month Rolling Expiry, GDPR & Compliance (anonymization)
4.6 Expired Balance Recovery
What it does: Admin can view all expired/dormant balances and recover them with an audit trail. Only account ID + amount is stored (no PII), so recovery requires the account ID.
Layman summary: "If a customer says 'I had a gift card that expired', staff can find and restore it."
Related: Admin Dashboard (Gift Card Management), Admin Audit Trail
4.7 VAT Treatment (SPV vs MPV)
What it does: Gift cards can be configured as Single-Purpose Vouchers (VAT charged at purchase) or Multi-Purpose Vouchers (VAT charged at redemption). Default is SPV.
Layman summary: "VAT is handled differently depending on the gift card type — charged at purchase or at use."
Related: VAT Calculation, Business Settings
4.8 Transaction Audit Log (gift_card_transactions)
What it does: Every action on a gift card is recorded in a dedicated audit table with transaction type, amount, reference, and acting user. Transaction types: purchase, topup, redeem_to_balance, payment (spent at checkout), refund (credited back), expire (background cleanup).
Layman summary: "Every penny that moves on a gift card is logged — who did what, when, and why."
Related: Admin Audit Trail, GDPR & Compliance
4.9 Admin Audit Trail (admin_audit_log)
What it does: Admin actions on gift card data (e.g., checking a user's balance) are logged to a separate audit table with admin ID, action type, target user, and JSON details. Used for compliance and dispute resolution.
Layman summary: "When staff look up a customer's gift card balance, it's logged with timestamps."
Related: Transaction Audit Log (gift_card_transactions), GDPR & Compliance
4.10 Idempotent Purchases
What it does: Gift card purchases use UUID-v4 idempotency keys to prevent double-charges. The system checks for existing payments with the same key before processing. Old keys are cleaned up after 24 hours by a cron job.
Layman summary: "If you click 'Buy' twice, you only get charged once."
Related: Double-Payment Prevention, Idempotency Keys
4.11 Refund to Gift Card
What it does: When a booking paid with a gift card is cancelled, the refund is credited back to the gift card's balance. If the card has expired, the money is retained by the salon (per policy).
Layman summary: "If you paid with a gift card and cancel, the money goes back on the card."
Related: Refunds, Cancellation
4.12 Friend Purchase Email Delivery (TODO)
What it does: When a user buys a gift card for a friend, the recipient's email is logged alongside the gift card code. The intent is to send the code via email automatically. Not yet implemented — SMTP integration is pending, and no admin notification is created (the gift code is logged and will be emailed once email delivery is wired).
Layman summary: "Buy a gift card for a friend and we'll email them the code — once email sending is set up."
Related: Online Gift Card Purchase, Notifications
5. Admin Dashboard
The central management hub for salon operations — managing users, bookings, services, scheduling, discounts, gift cards, portfolio, and business settings.
Related: Booking System, Payments, Loyalty & Discounts, Gift Cards, Today Page
5.1 User Management
What it does: List all users with search, view detailed user profiles (bookings, spend, visits, top services, patch tests, loyalty, referrals, privacy/consent), and manage customer relationships.
Layman summary: "See everything about a customer — their appointments, spending, favourite services, and more."
Related: Authentication & Security (roles), GDPR & Compliance (data export)
5.2 Booking Management
What it does: List all bookings with filters (date range, status, user), view details, edit services/notes/overrides, progress status, confirm pending bookings, cancel with refunds, and reschedule.
Layman summary: "Full control over every appointment — create, edit, reschedule, confirm, cancel."
Related: Booking System, Notifications (pending approvals)
5.3 Services & Custom Services Management
What it does: Full CRUD for the service catalog (name, price, duration, patch test settings). Custom services CRUD with promote-to-permanent and delete (with usage guard).
Layman summary: "Manage your price list and create one-off services for special requests."
Related: Services Catalog, Custom Services
5.4 Discount Campaigns Management
What it does: Create and manage discount campaigns (time-based, milestones, anniversary). Lifecycle: draft → active → completed → cancelled.
Layman summary: "Set up promotions like '10% off this week' or '15% off your 5th visit'."
Related: Loyalty & Discounts, Campaign Lifecycle
5.5 Gift Card Management
What it does: Create gift cards (all payment methods), top-up, transfer between cards, redeem to account balance, view/search cards, manage expired balances.
Layman summary: "Full gift card management — create, top up, transfer, and recover expired balances."
Related: Gift Cards, Till Purchases (POS), Expired Balance Recovery
5.6 Business Settings
What it does: Configure business name, address, VAT rate, gift card expiry months, and voucher type (SPV/MPV).
Layman summary: "Salon settings — VAT, gift cards, and contact info."
Related: VAT Calculation, VAT Treatment (SPV vs MPV), Contact & Location
5.7 Portfolio Image Upload
What it does: Upload portfolio images with tag management (categories and tags for filtering). Multi-format encoding pipeline.
Layman summary: "Add photos of your work to the gallery."
Related: Portfolio Gallery
5.8 Patch Test Management
What it does: Manage patch test definitions (which services need tests, notice period, expiry) and record customer patch test completions.
Layman summary: "Manage allergy test requirements and record when customers have had them."
Related: Patch Tests, Services Catalog
5.9 Till Purchases (POS)
What it does: Point-of-sale interface for selling gift cards at the counter (cash, card machine, saved card, online card entry, on the house). The saved-card path charges the customer's card-on-file directly via Square (pending-first, client-supplied UUID idempotency key — or a generated till- key when absent — with a bounded advisory try-lock keyed on that idempotency key); card-machine sales create a Square Terminal checkout and are completed by polling. Pending card till-sales are failed by the stale-pending sweep after Square's ~24h key-retention window.
Layman summary: "Sell gift cards at the till — take cash or card."
Related: Gift Cards, Purchase Methods, Payments
6. Loyalty & Discounts
A stamp-based loyalty program and automated discount campaigns that stack additively.
Related: Admin Dashboard (campaign management), Payments (discount application), Booking System (auto-apply on completion)
6.1 Stamp Loyalty
What it does: Customers earn 1 stamp per paid appointment (max 1 per calendar day). After 10 stamps, they get 10% off on their next booking. Stamps are refunded if the booking is cancelled.
Layman summary: "Buy 9 get 1 10% off. One stamp per appointment. After 10 stamps, the next one is 10% cheaper."
Related: Background Jobs (expired loyalty redemption cleanup), Frontend Architecture (stamp card UI)
6.2 Discount Campaigns
What it does: Automatic discount application at payment or completion time. Four campaign types:
- Time-based: Active during a date range (e.g., "10% off this week")
- Per-user milestone: Discount at the customer's Nth visit
- Global milestone: Discount when the salon reaches a total booking count (in-person only)
- Anniversary: Discount on the anniversary of the customer's first visit
Layman summary: "Automatic promotions — time-limited sales, loyalty rewards, and celebration discounts."
Related: Campaign Lifecycle, Additive Stacking
6.3 Additive Stacking
What it does: All eligible discounts stack together against the original booking total. E.g., loyalty 10% + time-based 5% = 15% off.
Layman summary: "Every discount you qualify for adds up. More discounts = more savings."
Related: Discount Campaigns, Stamp Loyalty
6.4 Campaign Lifecycle
What it does: Campaigns progress through states: draft → active → completed → cancelled. Status transitions are automated via hourly cron job (start on start_date, end on end_date or max redemptions).
Layman summary: "Set up a campaign now, schedule it to start next week, and it runs itself."
Related: Background Jobs (transition-discount-campaigns), Discount Campaigns Management
7. Today Page (Daily Operations Hub)
The staff's main daily dashboard for managing appointments in real time.
Related: Booking System, Admin Dashboard, Payments (tips, refunds)
7.1 Current & Next Appointment
What it does: Cards at the top showing the current in-progress appointment and the next one coming up. Auto-updates as time passes.
Layman summary: "See who's here now and who's next — at a glance."
Related: Booking Statuses & Transition Rules
7.2 Interactive Daily Calendar Grid
What it does: A visual timeline of today's appointments with time blockers shown as red/hatched bars. Drag-and-drop style interaction.
Layman summary: "Your day laid out on a timeline — appointments and blocked time."
Related: Time Blockers, Admin Schedule (Week View)
7.3 Pending Approvals Queue
What it does: List of bookings waiting for admin approval (new bookings, edit requests) with accept/decline buttons and side-by-side snapshots for edit requests.
Layman summary: "Approve or decline new bookings and change requests — all in one place."
Related: Booking Edit Requests, Notifications
7.4 Daily Summary / End of Day
What it does: When all appointments are done, replaces the current/next cards with a summary: customers served, total taken, tips, bookings made. Also shows weekly summaries on closed days.
Layman summary: "End-of-day report — customers seen, money taken, tips received."
Related: Tips, DoneForDay State
7.5 Walk-In & Call-In Wizards
What it does: Step-by-step wizards for creating walk-in (3-step) and call-in (4-step) bookings directly from the Today page.
Layman summary: "Quickly book someone in who walked in or called — step by step."
Related: Walk-In Booking (Admin), Call-In Booking (Admin)
7.6 DoneForDay State
What it does: When all appointments are completed/admin marks the day done, the system shows summary stats. Works across closed days and weekends with automatic week-range aggregation.
Layman summary: "Mark the day as done. The system shows you how it went."
Related: Daily Summary / End of Day
8. Authentication & Security
JWT-based authentication with refresh token rotation, role-based access control, progressive rate limiting, and account lockout.
Related: Frontend Architecture (auth store), GDPR & Compliance (data export includes session data)
8.1 Registration
What it does: New users create an account with email/password. Optional referral code. Password strength checked via zxcvbn (same algorithm client and server-side).
Layman summary: "Sign up with your email. Optionally enter a referral code."
Related: Guest Accounts, Referral System (referral code)
8.2 Login & JWT Tokens
What it does: Users log in with email/password. Receive a JWT access token (1-hour expiry) and a refresh token (90-day, single-use with rotation). The refresh token is consumed on each use — old token invalidated.
Layman summary: "Log in and stay logged in. Your session refreshes automatically."
Related: Auth Store & Token Management, Background Jobs (refresh token cleanup)
8.3 Role-Based Access Control
What it does: Five roles control what users can see and do:
unverified_email— registered but email not verified (limited access)verified_email— email verified (full customer access)admin— full salon managementguest— disposable booking-only accountaffiliate— referral tracking
Layman summary: "Different levels of access — customers see their own bookings, admins see everything."
Related: Guest Accounts, Admin Dashboard
8.4 Rate Limiting
What it does: Progressive dual-window rate limiting on login/register endpoints. Burst: 30 requests per 5 seconds. Sustained: 120 requests per 60 seconds. Violators get progressive delays (500ms → 10s).
Layman summary: "The system slows down rapid-fire requests to prevent abuse."
Related: Account Lockout
8.5 Account Lockout
What it does: After 5 failed login attempts, the account is locked for a progressively longer period (15min → 30min → 1h → 2h).
Layman summary: "Too many wrong passwords? You'll be locked out for a while."
Related: Rate Limiting
8.6 Email Verification & Password Reset
What it does: Backend supports sending verification codes for email verification and password reset. The frontend verification/reset UI is not yet wired (notable gap).
Layman summary: "The system can send verification codes, but the 'forgot password' link isn't on the website yet."
Related: Background Jobs (verification code cleanup)
8.7 Security Headers
What it does: Content-Security-Policy (default-src 'none'; frame-ancestors 'none'), CORS headers, and security middleware applied globally.
Layman summary: "Security headers protect against common web attacks."
8.8 Guest Accounts
What it does: Disposable accounts created on-the-fly for booking without registration. No identity tracking across bookings. PII scrubbed 6 months after the appointment.
Layman summary: "Book without signing up. Your details are automatically deleted after 6 months."
Related: Guest PII Anonymization, Self-Service Booking (Customer-Facing)
9. GDPR & Compliance
Full compliance with UK GDPR, including Article 15 data export, right to erasure, and data retention policies.
Related: Authentication & Security, Background Jobs (anonymization, financial cleanup), Notifications (user_id nullification)
9.1 GDPR Data Export (Article 15)
What it does: Customers can download all their personal data as a 23-section JSON file and PDF. Export runs in the background with 12-hour caching. Includes: profile, bookings, payments, patch tests, referrals (referred by + referred users), referral discounts, notification preferences, saved cards, refunds, social logins, loyalty redemptions, booking discounts, edit requests, affiliate payouts, forgiven no-shows, gift card balance, gift card transactions, gift cards, admin audit log, login history, name history, plus export metadata. Verification codes are explicitly excluded as authentication tokens.
Layman summary: "Download everything we know about you — in one click."
Related: Background Jobs (gdpr export cache cleanup), Frontend Architecture (GDPR page)
9.2 Account Deletion (Right to Erasure)
What it does: Customers can delete their account. The system scrubs PII from all child tables (social logins deleted, saved cards soft-deleted, verification codes expired, notes nulled, notification prefs deleted). Also scrubs external systems (S3 profile pictures deleted; Square saved cards deleted via the Square Cards API, and the stored square_card_id / square_customer_id references are NULLed so no Square identifiers survive the erasure).
Layman summary: "Delete your account and we wipe your data — everywhere."
Related: Saved Cards, SabreDAV (CardDAV) (contact deletion)
9.3 Guest PII Anonymization
What it does: Guest accounts with bookings older than 6 months are automatically anonymized: name → "Guest Anonymized", email → "anon-{id}@anon.invalid", phone zeroed, DOB reset. Saved cards are scrubbed too: square_card_id / square_customer_id NULLed and the cards soft-deleted with 7-year retained_until (matching §9.2).
Layman summary: "Guest details are automatically wiped after 6 months."
Related: Guest Accounts, Background Jobs (anonymize-stale-guest-accounts)
9.4 Financial Data Retention
What it does: Payment records retained for 7 years (UK HMRC + Limitation Act), then aggregated into monthly totals (no PII) and deleted.
Layman summary: "Payment records kept for 7 years for tax purposes, then anonymized."
Related: Background Jobs (cleanup-expired-financial-records)
9.5 Privacy Policy & Terms
What it does: Full privacy policy, terms of service, cancellation policy, and gift card terms & conditions available as user-facing pages.
Layman summary: "Legal documents — privacy, terms, and policies."
10. Portfolio Gallery
A visual gallery showcasing the salon's work with multi-format image support, tag-based filtering, and cursor-based pagination.
Related: Admin Dashboard (portfolio upload), S3/R2 Storage Abstraction, Multi-Format Image Pipeline
10.1 Image Upload
What it does: Admin uploads portfolio images with tags and categories. The system auto-generates multiple formats (AVIF, WebP, JPEG, JXL) for optimal browser delivery.
Layman summary: "Upload photos of your work with tags so customers can find them."
Related: Portfolio Image Upload
10.2 Multi-Format Image Pipeline
What it does: Client-side WASM encoding generates AVIF, WebP, JPEG, and JPEG XL variants. The <picture> element serves the best format the browser supports.
Layman summary: "Photos look great and load fast — the system picks the best format for your device."
Related: Frontend Architecture (ImageVariant component, workers)
10.3 Tag & Category Filtering
What it does: Fuzzy tag search (relevance-sorted results) and exact category filters (date-sorted results). Tag autocomplete for admin. Category counts show how many images in each.
Layman summary: "Find photos by tag or category — searching 'gel' shows all gel nail photos."
Related: Portfolio Image Upload
10.4 Cursor-Based Pagination
What it does: Images load in pages using cursor pagination (not page numbers). Infinite scroll style with a next_cursor field in responses.
Layman summary: "Scroll through the gallery — it loads more as you go."
11. Notifications
A pull-based admin notification queue with priority ordering. Note: email/SMS delivery is not yet implemented — notifications are UI-only.
Related: Booking System (creation/cancellation notifications), Payments (deposit notifications), Scheduling (hours change notifications), Gift Cards (friend purchase)
11.1 Admin Notification Queue
What it does: Internal notifications for the admin about important events: new bookings, cancellations, edit requests, deposit deadlines, unpaid reminders, and schedule changes.
All 14 notification reasons (DB enum):
| Reason | Trigger | Created By |
|---|---|---|
new_booking |
Every customer-created booking unconditionally | bookings.go:2328 |
pending_booking |
Booking needs admin approval (has notes or is today) | bookings.go:2349, manage.go:1693 |
cancelled_booking |
Booking cancelled by customer or admin | bookings.go:3330, manage.go:101,242 |
edit_requested |
User requests booking edit/reschedule | manage.go:1673 |
deposit_not_paid_by_deadline |
Deposit deadline passed in pending_release |
time-blockers.go:760 (cron every 5min) |
1_week_no_pay |
Booking ended 7-30 days ago with no payment | scheduled-cleanup.go:71 (cron daily 07:00) |
1_month_no_pay |
Booking ended 30+ days ago with no payment | scheduled-cleanup.go:139 (cron daily 07:30) |
gift_card_purchased_for_friend |
(defined in DB enum — code removed, email delivery pending) | Removed — email TODO |
default_hours_changed |
Staged hours change auto-applied at midnight | scheduled-cleanup.go:342 (cron daily 00:05) |
late_cancellation |
(defined in DB enum — NOT yet created by any code) | Planned |
rescheduled_booking |
(defined in DB enum — NOT yet created by any code) | Planned |
affiliate_claim |
(defined in DB enum — NOT yet created by any code) | Planned |
deposit_paid |
(defined in DB enum — NOT yet created by any code) | Planned |
edit_request |
(defined in DB enum — only edit_requested is used in code) |
Unused |
Layman summary: "Staff get notified when things happen — new bookings, cancellations, unpaid reminders."
11.2 Notification Bell & Unread Count
What it does: A bell icon in the navigation bar shows the number of unread notifications. Clicking it opens the notifications page.
Layman summary: "A bell icon shows how many new things need your attention."
Related: Frontend Architecture (NavBar component, auth store)
11.3 Acknowledge Flow
What it does: Notifications can be acknowledged (marked as read). Pagination with "show acknowledged" toggle lets admin review historical notifications.
Layman summary: "Mark notifications as read. You can still see old ones if needed."
Related: Pending Approvals Queue
11.4 User Notification Preferences
What it does: Users can set their notification preferences (email, SMS, push). Note: The table exists but no delivery system is wired — SMTP/SMS integration is not yet implemented — email/SMS delivery is a planned upcoming body of work.
Layman summary: "You can choose how you want to be notified (but notifications aren't being sent yet)."
Related: Frontend Architecture (account page preferences UI)
12. Infrastructure & DevOps
The platform infrastructure — Docker Compose stack, CI/CD, local development environment, and deployment configuration.
Related: Background Jobs (cron scheduler), S3/R2 Storage Abstraction
12.1 Docker Compose Stack
What it does: Five services orchestrated in Docker Compose:
- postgres:17 — Primary database
- backend — Custom Go API server (chi router)
- sabredav — PHP CardDAV/CalDAV server (profile photo sync)
- nginx — Reverse proxy (static frontend, API proxy, DAV proxy)
- rustfs — Local S3-compatible storage for dev
Layman summary: "Everything runs in Docker — database, API, web server, and file storage."
Related: Nginx Configuration, Local Development Setup
12.2 Nginx Configuration
What it does: Routes traffic:
/→ Static frontend files/api/*→ Backend Go server/dav/*→ SabreDAV server
Layman summary: "Nginx directs traffic to the right place — website, API, or contact sync."
Related: Docker Compose Stack
12.3 CI/CD Pipeline (Gitea)
What it does: Gitea CI pipeline that runs on every push: lints backend (go vet, golangci-lint, staticcheck, gosec), lints frontend (prettier, eslint), runs all tests, builds both binaries. Uses cached dependencies for speed.
Layman summary: "Every code change is automatically checked, tested, and built."
Related: Pre-commit Hooks
12.4 Pre-commit Hooks
What it does: Before every commit: auto-formats frontend with Prettier, lints frontend with ESLint, vet + lint the backend (if changed), runs secret scan (gitleaks).
Layman summary: "Code is cleaned and checked before it's saved."
Related: CI/CD Pipeline (Gitea)
12.5 Local Development Setup
What it does: A single ./local-dev-2.sh script launches a 4-pane tmux session with: psql console, Go dev server (hot-reload), Svelte dev server (HMR), and RustFS (local file storage). Seeds 20 users, 12 services, 43 bookings, and more.
Layman summary: "One command starts everything you need for local development."
Related: Docker Compose Stack, Build Tags (Dev vs Prod)
12.6 Build Tags (Dev vs Prod)
What it does: Go build tags switch between development mocks and production implementations:
dev→ Square mock client, RustFS storage, rate limiter disabled, local DB!dev(default) → Live Square API, Cloudflare R2 storage, production rate limiting
Layman summary: "Automatically switches between test-mode and real services depending on how you build."
Related: Square Terminal (In-Person Card) (dev mock)
12.7 SabreDAV (CardDAV)
What it does: CardDAV server for syncing customer contact info (profile photos) with compatible apps. Integrated at user registration (create contact), booking confirmation (calendar event), and account deletion (delete contact).
Layman summary: "Syncs customer contacts with your phone or address book."
Related: Account Deletion (Right to Erasure)
12.8 S3/R2 Storage Abstraction
What it does: Abstracted storage layer for images and profile pictures. Uses RustFS (local filesystem via S3-compatible API) in development, Cloudflare R2 in production.
Layman summary: "Photo storage that works locally and in the cloud."
Related: Portfolio Gallery, Profile Picture Upload (Admin)
13. Background Jobs (Cron Scheduler)
A centralized cron scheduler that runs 23 maintenance jobs for cleanup, transitions, and data management.
Related: Availability & Scheduling (hours apply), GDPR & Compliance (cleanup), Gift Cards (expiry/cleanup), Payments (idempotency cleanup)
13.1 Every 5 Minutes
- cleanup-reservations: Delete expired slot reservations by TTL type
- cleanup-expired-deposits: Move unpaid bookings to
pending_releasestate - cleanup-rate-limiters: Purge stale rate limiter entries
- cleanup-gdpr-export-cache: Expire old GDPR export caches
- sweep-pending-square-refunds: Reconcile/retry stuck Square refunds (23h age guard; aggregates per charge, single refund per charge, resolves by Square status, notifies on failure) — starts on the
:00ticks - sweep-stale-pending-payments: Fail stale pending payments and till-sales older than Square's ~24h idempotency-key retention, so a late retry is cleanly rejected instead of issuing a second charge — starts on the
:01ticks, offset one minute from the refund sweep to avoid table contention - sweep-stale-terminal-checkouts: Cancel card-machine (Terminal) checkouts still pending at Square after an hour, so a never-polled checkout cannot complete into an invisible, untracked charge — every 15 minutes
Related: Slot Reservation TTLs, Deposit System, Payments (refund sweeps, stale-pending payment sweep, terminal checkout sweep)
13.2 Every Minute
- cleanup-progressive-rate-limiter: Clean progressive rate limiter state
Related: Rate Limiting
13.3 Hourly
- cleanup-expired-loyalty-redemptions: Expire unused loyalty discount windows
- cleanup-old-idempotency-keys: Purge old booking/payment dedup keys
- cleanup-revoked-jtis: Clean expired revoked JWT entries
- cleanup-stale-login-entries: Clean old login attempt records
- transition-discount-campaigns: Advance campaign lifecycle (draft→active, active→completed)
Related: Idempotency Keys, Campaign Lifecycle, Account Lockout
13.4 Daily (Staggered)
- 00:05 —
apply-default-hours: Apply pending scheduled hours changes - 02:00 —
cleanup-verification-codes: Purge expired verification codes - 02:00 —
cleanup-refresh-tokens: Purge expired refresh tokens - 03:00 —
anonymize-stale-guest-accounts: GDPR anonymize guests >6mo inactive - 03:30 —
cleanup-idle-accounts: Clean idle gift card accounts (2yr/5yr) - 04:00 —
cleanup-expired-financial-records: Purge records beyond 7yr retention - 04:30 —
cleanup-old-name-history: Prune old name change history - 05:00 —
cleanup-expired-gift-cards: Expire gift cards after 24 months - 07:00 —
notify-unpaid-1-week: Create notification for bookings unpaid >1 week - 07:30 —
notify-unpaid-1-month: Create notification for bookings unpaid >1 month
Related: Staged Default Hours Changes, Guest PII Anonymization, 24-Month Rolling Expiry, Idle Account Cleanup, Financial Data Retention
Layman summary: "The system takes care of itself — cleaning up old data, applying schedule changes, anonymizing guest info, and sending reminders — all on a timer."
14. Contact & Location
Dynamic business contact information and map display.
Related: Admin Dashboard (Business Settings), Frontend Architecture (Map component)
14.1 Contact Page
What it does: Displays the salon's contact info (phone, email, address) from the first admin user's profile. Includes a MapLibre GL interactive map.
Layman summary: "Find the salon — address, phone, and an interactive map."
14.2 Admin Contact / Business Info
What it does: The salon can update contact info through business settings. The contact page reflects changes automatically.
Layman summary: "Change your contact details and they update on the website."
Related: Business Settings
15. Frontend Architecture
The SvelteKit 5 static SPA that powers the entire user interface.
Related: Authentication & Security (auth store), Portfolio Gallery (ImageVariant component)
15.1 SvelteKit Routes
What it does: All frontend pages and their purposes:
/— Home page (welcome, services overview, portfolio carousel)/book— 5-step booking wizard/login— Login/Register/account— User profile, bookings, loyalty, gift cards, GDPR export/admin— Admin dashboard/today— Daily operations hub/portfolio— Image gallery/prices— Service price list/schedule— User's upcoming appointments/admin/schedule— Weekly calendar (Google Calendar-style)/contact— Business info + MapLibre map/gdpr— GDPR data export/admin/notifications— Notification queue/tip— Tip payment page (shared TipPayment component)/pay-tip/[id]— Tip payment page/privacy-policy— Privacy policy page/terms— Terms & conditions page
Related: Self-Service Booking (Customer-Facing), Admin Dashboard, Today Page, Contact Page
15.2 Shared UI Components
What it does: A library of reusable UI components built with shadcn-svelte:
PhoneInput— UK phone validation with auto-formattingCharCounter— Grapheme counter usingIntl.Segmenter(colour-coded at thresholds)ImageVariant— Multi-format<picture>element for responsive imagesNavBar— Responsive navigation with notification badgeMap— MapLibre GL interactive map- Various shadcn components: buttons, cards, dialogs, inputs, badges, separators, skeletons
Related: Multi-Format Image Pipeline, Notification Bell & Unread Count
15.3 Timezone Handling
What it does: All timestamps are stored in UTC. The backend uses clock.Now() (returns UTC). The frontend converts between UTC and the browser's local time (always Europe/London since the app is UK-only) using formatLocalDateTime() and parseWallClockDate().
Layman summary: "Times are stored in UTC and shown in UK time — no confusion about timezones."
Related: Available Hours Calculation
15.4 Auth Store & Token Management
What it does: JWT tokens stored in localStorage with automatic hourly refresh. Refresh token rotates each use (old token invalidated). Role-based UI via hasRole(), isAdmin(), isVerified().
Layman summary: "Stays logged in. Automatically refreshes your session. Shows different options based on who you are."
Related: Login & JWT Tokens, Role-Based Access Control
Known Limitations
Documented gaps and missing functionality (from codebase audit):
- Single employee — No multi-staff scheduling
- Email/SMS pending (planned) — SMTP not wired yet; notifications are UI-only; email integration is a planned upcoming body of work
- Production S3/R2 pending (planned) — prod storage stubs return "not implemented" until the S3 integration body of work lands
- No social auth — OAuth provider registrations pending
- No dark mode, PWA, recurring bookings, CSV export
- Password reset — Backend exists, no frontend link
- No error tracking — Sentry not configured
- No automated DB backups — No pg_dump cron
- No API docs — No OpenAPI/Swagger spec
Cross-Reference Index
| Feature | Related Areas |
|---|---|
| Booking System | Payments (deposits, refunds), Scheduling (availability), Services Catalog, Patch Tests, Notifications (approvals, cancellations), Background Jobs (cleanup) |
| Payments | Gift Cards (purchase/redeem), VAT (SPV/MPV), Booking (deposits), Loyalty (discount application), Admin (POS till), Background Jobs (idempotency cleanup) |
| Gift Cards | Payments (purchase/redeem/refund), VAT (SPV/MPV), Admin (management), Notifications (friend purchase), Background Jobs (expiry, idle cleanup) |
| Scheduling | Booking (availability calculation), Admin (schedule management), Notifications (hours change), Background Jobs (apply-default-hours) |
| Admin Dashboard | Every other feature (central management hub for all operations) |
| Loyalty & Discounts | Payments (application at completion), Booking (auto-apply on completion), Admin (campaign management), Background Jobs (campaign transitions, redemption expiry) |
| Today Page | Booking (progress status, walk-in/call-in), Payments (tips, refunds), Scheduling (time blockers, daily view) |
| Authentication | Everything (gate across all features), Frontend (auth store), Background Jobs (token/login cleanup) |
| GDPR & Compliance | Auth (account deletion), User Management (data export), Background Jobs (anonymization, financial cleanup), Notifications (user_id nullification) |
| Portfolio Gallery | Admin (image upload), Frontend (ImageVariant, workers), Infrastructure (S3/R2 storage) |
| Notifications | Booking (creation/cancellation/edit), Payments (deposits), Scheduling (hours change), Gift Cards (friend purchase), Background Jobs (unpaid reminders) |
| Infrastructure | Background Jobs (cron scheduler), Portfolio (S3 storage), SabreDAV (contacts), Auth (CSP headers, CORS) |
| Background Jobs | Scheduling (hours apply), GDPR (cleanup/anonymization), Gift Cards (expiry), Payments (idempotency), Loyalty (campaign transitions), Auth (token cleanup) |
| Contact & Location | Admin (business settings), Frontend (MapLibre map component) |
| Frontend Architecture | Auth (auth store), Portfolio (ImageVariant), Notifications (bell badge), Booking (wizard components), Scheduling (time slot picker) |
This catalog was compiled from a full codebase audit of the Crussell platform. Each feature has been traced through backend handlers (Go), frontend components (SvelteKit), database schema (PostgreSQL), and infrastructure configuration (Docker/nginx).