Files
Crussell/obsidian/Crussell/Feature Catalog.md
T
popertots cec7167469 Update docs for payment remediation: test counts, advisory locks, de-scope note
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.
2026-08-22 00:34:49 +01:00

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) → confirmedin_progresscompleted (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_cancelled
  • confirmed → in_progress, completed, client_cancelled, we_cancelled
  • in_progress → completed
  • pending_release → pending, confirmed, client_cancelled, we_cancelled
  • no_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 management
  • guest — disposable booking-only account
  • affiliate — 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."


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_release state
  • 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 :00 ticks
  • 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 :01 ticks, 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:05apply-default-hours: Apply pending scheduled hours changes
  • 02:00cleanup-verification-codes: Purge expired verification codes
  • 02:00cleanup-refresh-tokens: Purge expired refresh tokens
  • 03:00anonymize-stale-guest-accounts: GDPR anonymize guests >6mo inactive
  • 03:30cleanup-idle-accounts: Clean idle gift card accounts (2yr/5yr)
  • 04:00cleanup-expired-financial-records: Purge records beyond 7yr retention
  • 04:30cleanup-old-name-history: Prune old name change history
  • 05:00cleanup-expired-gift-cards: Expire gift cards after 24 months
  • 07:00notify-unpaid-1-week: Create notification for bookings unpaid >1 week
  • 07:30notify-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-formatting
  • CharCounter — Grapheme counter using Intl.Segmenter (colour-coded at thresholds)
  • ImageVariant — Multi-format <picture> element for responsive images
  • NavBar — Responsive navigation with notification badge
  • Map — 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).