Backend:
- Add enriched response types (EditSnapshot, EnrichedEditRequest) with original vs proposed snapshots
- Add 4 new GET endpoints for viewing edit requests (user and admin scoped)
- Remove github.com/lib/pq dependency — use native PostgreSQL array scanning
- Clean up edit requests, time blockers, and notifications on booking cancellation
- Validate exceptional closed hours on admin approve (409 Conflict)
- Notification upsert on edit request replace (no duplicate admin notifications)
Frontend:
- New user EditRequestModal with time/services/both modes and lunch protection
- New admin EditRequestModal with side-by-side diff (date/time, services, notes)
- Integrate edit requests into PendingApprovals card and notifications page
- Preload 3 months of availability to prevent calendar snap-back
- Apply lunch protection to isDateUnavailable in BookingFlow and BookingCreateModal
- Fix accessibility: card list items use <button> instead of <div>
Dev & Docs:
- Seed edit requests in local-dev-2.sh
- Update all Obsidian manuals with enriched edit request documentation
- 42 new tests (438/441 passing)
Replace placeholder BookingModal on the Next Appointment edit button with a
dedicated EditBookingModal that allows admins to add, remove, and override
services on an active booking. Includes backend PUT endpoint with overlap
detection and full test suite (20 tests).
- Add TestMain to all 10 test packages (schema DROP+CREATE runs once per package)
- Convert per-test setupTestDB to resetTestData (TRUNCATE only, ~60% faster)
- Add 3 missing tables to TruncateTables (booking_edit_requests, exceptional_group_applications, business_settings)
- Remove dead truncateDiscountTables helper
- Consolidate discount_test.go into package bookings (was external test package)
- Update testutils.SetupTestDB to truncate-only
- Fix unused imports across user, bookings, and handlers packages
- Verify: 286 passing, 2 skipped, 0 failures with -count=2 (no state leakage)
- Add time blocker management for booking edit requests
- Add closing_time field to admin today/current-next endpoint
- Update UserBookingModal and CurrentAppointment UI components
- Fix fmt import in bookings_test.go (was missing)
- Fix created_by FK in TestAdminApproveEditRequest_TimeBlockerOverlap
- Update test coverage for edit request time blocker overlap
- Update gap backlog documentation
- Add new discount types to testdb.Migrate drop list (campaign_type, milestone_type, etc.)
- Add new discount tables to testdb.Migrate drop order and TruncateTables
- Move discount type definitions to top of init-script.sql with other enums
- Remove duplicate type definitions that were after admin_notification_reason
- Types must be defined before tables that reference them
- Remove all discount_eligible checks from CreateBookingHandler and AdminCreateBookingForUserHandler
- Discounts are only calculated at completion/payment time, not at booking time
- Remove discount_eligible from frontend Booking type and pending_redemption from BookingUser
- Remove TestDiscount_EligibilityFlag test (no longer relevant)
- Fix modal scrolling: add max-h-[90vh] overflow-y-auto to match ServicesManagement pattern
- Lower default discount from 10% to 5%
- Clarify max_redemptions label as 'campaign total' (per campaign, not per person)
- Remove automatic deposits_required=3 on no-shows, give admin flexibility
- Add tests for no-show deposit logic (forgiven, over 24h, under 24h)
- Add tests for reservation cleanup TTL (admin walk-in/call-in 15min)
- Add tests for EXIF GPS data stripping in portfolio images
- Add tests for contact info endpoint
- Add tests for guest account anonymization
- backend/handlers/bookings/admin_reserve.go:
- Add explicit reservation_type field ("walkin" | "callin") to request struct
- Remove TTL-based heuristic for type detection
- Walk-in: uses duration_minutes, allows null user_id, 1min past grace
- Call-in: requires service_ids, validates future time, calculates duration from services
- Both types now use 15-minute TTL
- backend/handlers/scheduling/time-blockers.go:
- Update CleanupOldReservations: both walkin and callin use 15min TTL (was 10min/60min)
- frontend/WalkInBooking.svelte:
- Full rewrite of reservation logic
- If available now and >15min remaining: reserve from now to slot end
- If <=15min or not available: reserve next full slot
- Always reserves before opening modal (never open without hold)
- Passes reservedDuration to modal
- TTL changed from 5 to 15 minutes
- frontend/WalkInCreateModal.svelte:
- Replace dead commented-out guest code with working guest creation
- Guest account created at submit time (not earlier)
- Phone defaults to +447700900000 if blank
- Phone field marked optional with helper text
- Name split into firstName/lastName for backend
- Validation relaxed: only name required for guests
- frontend/BookingCreateModal.svelte:
- TTL changed from 60 to 15 minutes
- Add reservation_type: "callin" to reserve payload
- Guest creation uses correct firstName/lastName fields
- Default guest phone to +447700900000
- Reservation no longer requires selectedUserId (works for guests)
- docs: Update Future Work backlog to mark completed items
- backend/handlers/user/account.go: Wire DELETE /api/user/account to call
anonymize_user() for registered users and delete_guest_user() for guests,
with CardDAV contact cleanup
- backend/handlers/user/profile_test.go: Add TestAccount_DeleteGuest and
enhance TestAccount_Delete to verify anonymization results
- backend/main.go: Add GET /api/health endpoint with DB ping and S3 status
check; add HSTS and Referrer-Policy security headers; replace
http.ListenAndServe with http.Server + graceful SIGTERM/SIGINT shutdown
- frontend/routes/+layout.svelte: Replace alert() with toast notifications
for email verification flow
- frontend/routes/login/+page.svelte: Replace alert() with toast.info for
social login prototype buttons
- frontend/booking/BookingFlow.svelte: Remove 2 console.log debug calls;
add cancellation policy note in Step 3; add timezone policy comment
- frontend/ImageUpload.svelte: Comment out debug console.log
- init-scripts/init-script.sql: Add delete_guest_user() SQL function
- docs: Update README.md and Obsidian notes to reflect completed items
- backend/main.go: Flatten /bookings/* sub-Route to explicit paths to prevent
RequireAuth middleware from bleeding into OptionalAuth POST /bookings
- backend/handlers/scheduling/time-blockers.go: Exclude RESERVATION:* entries
from GetTimeBlockersInRange so overlap checks dont reject the users own
reservation before CreateBookingHandler can delete it
- local-dev-2.sh: Fix open_day to skip Saturday (6) not Monday (1), matching
working_hours schema; move guest booking dates to +16/+20/+22 days beyond
the upcoming loop range; add reserve-then-book step mirroring frontend flow
CleanupOldReservations now handles four reservation types:
logged-in (1hr), anonymous (10min), admin-walkin (10min), admin-callin (1hr).
AnonymizeStaleGuestAccounts scrubs PII from guest accounts whose last booking
was 6+ months ago and who have no pending/confirmed bookings. Financial records
remain intact — only personal data is wiped (UK GDPR compliance).
Triggered on every GetAvailableHours call.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Guest flow: CreateGuestUserHandler creates disposable guest accounts on-the-fly.
CreateBookingHandler uses OptionalAuth — accepts authenticated or guest (user_id
in body, validated as account_role='guest'). Guests bypass deposits, patch tests,
and the 24h deposit advance rule.
Admin reserve: AdminReserveSlotHandler supports walk-in (5min TTL) and call-in
(60min TTL) reservations with configurable TTL. Validates against bookings,
blockers, working hours.
Route restructuring: POST /bookings moved to OptionalAuth group. POST /bookings/reserve
added for public reservation. POST /admin/bookings/reserve added for admin.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add POST /api/bookings/reserve route with OptionalAuth middleware and rate limiting.
Update CleanupOldReservations to handle dual TTLs: 1hr for logged-in users,
10min for anonymous reservations. Update CreateBookingHandler to also match
anon reservations by start_time for users who register mid-flow.
Add comprehensive tests for reservation creation, validation, conflict detection, and cleanup.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
OptionalAuth extracts user info from Bearer token if present,
otherwise passes through without requiring authentication.
ReserveSlotHandler creates temporary time_blockers reservations:
- Logged-in users: max 1 active, 1hr TTL, tracked by user_id
- Anonymous users: global cap of 50, 10min TTL, tracked by IP hash
- Validates slot availability (working hours, booking overlap, blocker overlap)
- Returns reservation ID, start_time, duration, and expires_at
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Frontend now compresses and strips metadata before upload.
Skip backend re-encoding to preserve quality and reduce latency.
Extract independent extensions for main file vs thumbnail.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>