Gift-card rolling expiry (setting-driven, was dead config):
- GetGiftCardExpiryMonths(): single source of truth (business_settings
gift_card_expiry_months, fallback 24) shared by payment handlers and the
CleanupExpiredGiftCards job (was hardcoded 24).
- expiry_date now maintained on ALL 9 gift-card write sites (buy, topup,
transfer, redeem, terminal payment, refund credit, till) so the refund-time
guard at refunds.go actually fires. Schema default 12->24 + migration note;
test-DB seed aligned. Stale "expiry_date IS NULL" test rewritten; new
expired-card-rejected regression test.
Frontend SvelteDate purge (docs' stated convention, wide):
- All 180+ raw `new SvelteDate(...)` uses across routes/components replaced
with parseWallClockDate (backend UTC ISO) or new Date (wall-clock
constructors). SvelteDate imports removed. timeSlots.ts getDayWithOrdinal
fixed. Zero SvelteDate references remain; svelte-check clean.
Strict timezone/DST testing + QA fixes:
- 8 new hermetic boundary tests: clock.DST transitions (both 2026 folds),
closing-hours GMT vs BST, booking date-window midnight, refund-tier
elapsed-time independence, deposit-window UTC-instant, scheduling
LondonDateString midnight, today AT TIME ZONE window + UTC round-trip.
- today.go summary date labels fixed to London wall-clock (were showing the
previous UTC day during BST) + regression test.
- pgx ScanLocation fixed to UTC via AfterConnect (was host-local -> JSON
offsets depended on deployment TZ, contradicting the documented UTC
invariant) + regression test. Registered as a new *Type to avoid a data
race on the shared type map (caught by -race).
Admin Business Settings (setting now functional => legal floor):
- gift_card_expiry_months validation floor raised 1 -> 12 months (CMA/
Consumer Rights Act 2015 unfair-contract-term guidance) in endpoint + UI,
with rolling-expiry semantics shown in both display and edit form.
- 3 new expiry validation tests; 2 pre-existing message assertions updated.
Full suite 25/25 + race clean via run-tests.sh lockfile; svelte-check 0
errors/warnings; production build succeeds.
bits-ui blocks onValueChange for unavailable dates, leaving stale selection. Adds native click detection on [data-unavailable] elements to call onchange(undefined) so the time picker hides and the user knows nothing was picked.
Add extractErrorMessage helper for JSON error body parsing and apply sanitizeText across all toast displays. Add time_blockers test coverage for new holiday placeholder cleanup and overlapping scenarios.
Centralizes auth token management into a reusable apiFetch() helper and getAuthHeaders() utility, eliminating inline Bearer token logic across all frontend files.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Release reservation on service change, date change, and step-back navigation
- Add synchronous double-click payment guard (isProcessingPaymentSync)
- Immutable update for confirmedBooking to prevent race-condition overcharge
- Generate fresh idempotency key per submission attempt (was reused across
component lifetime, causing stale-booking illusion on re-submit)
- Release reservation after successful booking submission
- Extract releaseReservation() helper for DRY reservation cleanup
- Race-condition guard in selectTimeWithValidation: pass clicked time
explicitly so stale validation can't clobber a newer selection
Introduce getLondonTodayCalendarDate(), parseWallClockDate(), and formatLocalDateTime() for reliable Europe/London timezone handling. Replace ad-hoc SvelteDate/new Date() usage with these utilities across all components and stores.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add proper keys to #each blocks across 15+ components to fix reordering bugs. Replace new Date() with SvelteDate in reactive contexts. Use $derived for computed values (totalPages). Use resolve() from $app/paths for all internal navigation hrefs. Add ARIA labels and keyboard accessibility to NavBar mobile menu. Remove unused handleRetry from UserPaymentModal.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
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)