From 0dff41a4ee3a688b367cd347c61dbe1dd414478b Mon Sep 17 00:00:00 2001
From: Stephen Adamson
Date: Thu, 25 Jun 2026 14:31:49 +0100
Subject: [PATCH] =?UTF-8?q?style:=20fix=20svelte/no-useless-mustaches=20?=
=?UTF-8?q?=E2=80=94=20unwrap=20string=20literals=20from=20mustache=20expr?=
=?UTF-8?q?essions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../plans/timezone-tx-migration-completion.md | 486 ------------------
.../components/admin/BusinessSettings.svelte | 2 +-
.../lib/components/admin/TimeBlockers.svelte | 4 +-
.../booking/SelectedTimeSummary.svelte | 4 +-
.../lib/components/today/TodayCalendar.svelte | 4 +-
5 files changed, 7 insertions(+), 493 deletions(-)
delete mode 100644 .sisyphus/plans/timezone-tx-migration-completion.md
diff --git a/.sisyphus/plans/timezone-tx-migration-completion.md b/.sisyphus/plans/timezone-tx-migration-completion.md
deleted file mode 100644
index 66747d3..0000000
--- a/.sisyphus/plans/timezone-tx-migration-completion.md
+++ /dev/null
@@ -1,486 +0,0 @@
-# Timezone + Transaction Migration — Completion Plan
-
-## Workflow per Issue (MANDATORY)
-
-For EVERY issue in this plan, follow this exact sequence:
-
-```
-Step 1: IMPLEMENT the fix (delegate to subagent)
-Step 2: WAIT for subagent to complete
-Step 3: VERIFY — Read the changed files, run lsp_diagnostics, run go build
-Step 4: CODE REVIEW — Manually inspect the diff. Check:
- - Does the fix match the intended pattern?
- - Are there edge cases the fix misses?
- - Does it follow existing codebase conventions?
-Step 5: ADD BACKEND TESTS for the fix (delegate to subagent)
-Step 6: WAIT for test subagent to complete
-Step 7: VERIFY tests — lsp_diagnostics, go build, go test on the relevant package
-Step 8: UPDATE todo — mark issue complete
-Step 9: MOVE TO next issue
-```
-
-**NO parallel execution across issues.** Each issue is a lock-step sequence.
-
-## Ground Rules
-
-- **NO `git checkout`**, `git stash`, `git reset`, `git restore`, or any destructive git commands.
-- **NO `rm -rf`**, mass file deletion, or destructive filesystem operations.
-- **Do NOT commit** unless explicitly asked.
-- **Do NOT create new files** unless the plan explicitly says to. Prefer editing existing files.
-- **Stage only intended files** if you do stage anything.
-- **Every delegation prompt** must include the 6-section structure:
- 1. TASK — atomic, specific goal
- 2. EXPECTED OUTCOME — concrete deliverables
- 3. REQUIRED TOOLS — explicit whitelist
- 4. MUST DO — exhaustive requirements
- 5. MUST NOT DO — forbidden actions
- 6. CONTEXT — file paths, existing patterns, constraints
-
----
-
-## Issue 1: Fix `calculateAge()` in format.ts
-
-**File:** `/home/popertots/Crussell/frontend/src/lib/utils/format.ts`
-**Lines:** ~92-101
-**Bug:** Uses browser-local `getFullYear()/getMonth()/getDate()` for age calculation.
-
-### Step 1 — Implement fix
-Delegate to a `deep` or `unspecified-high` agent. Use Read + Edit tools only.
-
-**Prompt:**
-```
-TASK: Fix calculateAge() function in /home/popertots/Crussell/frontend/src/lib/utils/format.ts to use Europe/London timezone.
-
-EXPECTED OUTCOME: calculateAge() returns correct age for all UK users regardless of browser timezone. Function signature, return type, and all callers remain unchanged.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- Read lines 85-105 of the file to see the current implementation
-- Parse the DOB string directly (split by '-') to get year/month/day
-- Get today's London date using toLocaleDateString('en-CA', { timeZone: 'Europe/London' })
-- Compute age: difference in years, subtract 1 if birthday hasn't occurred yet in London time
-- Return integer (years)
-
-MUST NOT DO:
-- Do not change function signature or how callers use it
-- Do not import external date libraries
-- Do not modify any other function in format.ts
-
-CONTEXT: Used during registration to validate minimum age (16+). DOB comes from form as "YYYY-MM-DD" string. UK-only app.
-```
-
-### Step 3 — Verify
-Read the changed function. Confirm:
-- DOB is parsed by string split (not via `new Date(dob)`)
-- London date uses `toLocaleDateString('en-CA', { timeZone: 'Europe/London' })`
-- Age computation handles month/day comparison correctly
-- Run `lsp_diagnostics` on format.ts
-
-### Step 5 — Add backend tests (skip — this is frontend-only code)
-
----
-
-## Issue 2: Fix 4 display components missing `timeZone: 'Europe/London'`
-
-### Step 1 — Implement fix
-Delegate to a `visual-engineering` or `deep` agent. Read each file, then edit.
-
-**Files to fix:**
-1. `/home/popertots/Crussell/frontend/src/lib/components/admin/CustomServicesManagement.svelte` — line ~289
-2. `/home/popertots/Crussell/frontend/src/lib/components/admin/GiftCardsManagement.svelte` — line ~752
-3. `/home/popertots/Crussell/frontend/src/routes/gdpr/+page.svelte` — lines ~262 and ~268
-
-**Prompt:**
-```
-TASK: Add timeZone: 'Europe/London' to toLocaleDateString/toLocaleString calls in 4 locations across 3 frontend files.
-
-EXPECTED OUTCOME: All date displays use Europe/London timezone so that UTC timestamps near midnight display the correct London date.
-
-REQUIRED TOOLS: Read, Edit, Grep
-
-MUST DO:
-- Read each file at the specified line to understand the context
-- Determine if the value being formatted is a datetime (needs timezone) or date-only (safe as-is)
-- Add timeZone: 'Europe/London' to the options object where needed
-- The call sites are:
- 1. CustomServicesManagement.svelte:~289 — likely a toLocaleDateString for expiry/schedule display
- 2. GiftCardsManagement.svelte:~752 — gift card date display
- 3. gdpr/+page.svelte:~262 — some GDPR-related date
- 4. gdpr/+page.svelte:~268 — some GDPR-related date
-
-MUST NOT DO:
-- Do not change any logic other than adding the timeZone option
-- Do not modify any CSS or HTML structure
-
-CONTEXT: UK-only booking platform. UTC timestamps near midnight display wrong London date without explicit timeZone option.
-```
-
-### Step 3 — Verify
-Read each changed file. Confirm the `timeZone: 'Europe/London'` was added correctly.
-Run `lsp_diagnostics` on each changed file (if the LSP supports Svelte).
-
-### Step 5 — Add backend tests (skip — these are frontend-only changes)
-
----
-
-## Issue 3: Fix remaining `toISOString().split('T')[0]` calls
-
-### Step 1 — Implement fix
-Delegate to a `deep` or `visual-engineering` agent.
-
-**Files:**
-1. `/home/popertots/Crussell/frontend/src/routes/login/+page.svelte` — line ~491
-2. `/home/popertots/Crussell/frontend/src/routes/gdpr/+page.svelte` — line ~467
-
-**Prompt:**
-```
-TASK: Replace 2 remaining toISOString().split('T')[0] calls with toLocaleDateString('en-CA', { timeZone: 'Europe/London' }) in the frontend.
-
-EXPECTED OUTCOME: No more toISOString().split('T')[0] patterns remain in the frontend. Date-only strings are produced via London-timezone-aware formatting.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- Read login/+page.svelte around line 491 — replace toISOString().split('T')[0] with toLocaleDateString('en-CA', { timeZone: 'Europe/London' })
-- Read gdpr/+page.svelte around line 467 — same replacement
-- Verify the context: login uses the value as a max date attribute; gdpr uses it in a JSON filename
-
-MUST NOT DO:
-- Do not change any surrounding logic
-- Do not remove the +00:00 or any other API communication toISOString() calls that were confirmed safe
-
-CONTEXT: Both are cosmetic — login date input max value and GDPR JSON filename. But keeping them consistent prevents future timezone bugs.
-```
-
-### Step 3 — Verify
-Read both changed files. Confirm the replacement intent (date-only, not API communication).
-
-### Step 5 — Add backend tests (skip — frontend-only changes)
-
----
-
-## Issue 4: Fix 3 genuine payment bugs
-
-**Background:** The reference pattern is `ProcessPendingSquareRefunds` in `refunds.go`. It inserts records with `status='pending'` inside the transaction, commits, THEN calls the Square API. The schema already supports this: `payment_status` ENUM has `'pending'`, and all tables default to it.
-
-### Step 1 — Fix Issue 4a: CreateTipPayment Square-BEFORE-tx
-
-**File:** `/home/popertots/Crussell/backend/handlers/payments/handlers.go`
-**Lines:** ~1700-1752
-
-**Prompt:** (delegate to `unspecified-high` agent)
-```
-TASK: Fix CreateTipPayment handler in /home/popertots/Crussell/backend/handlers/payments/handlers.go so the Square API call happens AFTER the DB transaction commit, not before.
-
-EXPECTED OUTCOME: Square CreatePayment is called only after the DB transaction commits successfully. If the transaction fails, Square is never called. No money lost.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- Read lines 1690-1760 to understand the current flow
-- Restructure so: start tx → INSERT payment with status='pending' and square_payment_id=NULL → tx.Commit() → call SquareClient.CreatePayment() → if success, UPDATE payment SET status='completed', square_payment_id=$1
-- Keep the same idempotency key pattern
-- Keep the same error logging
-- The payment record already has a `status` column (payment_status ENUM, defaults to 'pending')
-
-MUST NOT DO:
-- Do not change the function signature
-- Do not change any other handler in the file
-- Do not remove the advisory unlock or any existing cleanup
-
-CONTEXT: Currently Square is called at line ~1711 BEFORE the tx starts at ~1732. If tx.Commit() fails, the Square charge is orphaned with no recovery path.
-```
-
-### Step 3 — Code Review for 4a
-Read the changed handler. Verify:
-- tx begins FIRST
-- Payment record inserted BEFORE Square call with `status='pending'`
-- `tx.Commit()` happens BEFORE `SquareClient.CreatePayment()`
-- Square result updates the record to `'completed'`
-- Error handling covers: tx.Commit() fails (return 500, no Square charge), Square fails (leave as 'pending' for manual retry)
-- `go build ./handlers/payments/` passes
-
-### Step 5 — Add backend tests for 4a
-Delegate to subagent:
-```
-TASK: Add test coverage for the CreateTipPayment fix in handlers/payments/payments_test.go.
-
-EXPECTED OUTCOME: A test that verifies when CreateTipPayment's tx.Commit() fails, the Square API is never called, and when tx.Commit() succeeds, Square IS called.
-
-REQUIRED TOOLS: Read, Edit, Grep
-
-MUST DO:
-- Read the existing test patterns in payments_test.go (mock Square client, test HTTP handlers)
-- Create a test that:
- 1. Sets up a booking with a tip amount
- 2. Mocks SquareClient.CreatePayment to succeed
- 3. Calls CreateTipPayment handler
- 4. Asserts the payment record exists with status='completed'
-- Create another test that:
- 1. Sets up a booking with a tip amount
- 2. Mocks the DB to fail on commit (use test context cancellation or tx rollback trigger)
- 3. Verifies Square.CreatePayment was NOT called
- 4. Verifies 500 status is returned
-
-MUST NOT DO:
-- Do not modify production code (only add tests)
-- Do not use external mocking frameworks (use existing mock patterns)
-- Follow existing test file conventions
-
-CONTEXT: Payment tests use mock square client. Test fixtures create bookings. Test transactions use testutils.SetupTestTx(t).
-```
-
-### Step 7 — Verify tests for 4a
-Run `go test ./handlers/payments/ -run TestCreateTipPayment -v -count=1` — confirm it passes.
-
----
-
-### Step 1 — Fix Issue 4b: BuyGiftCard Square-BEFORE-tx
-
-**File:** `/home/popertots/Crussell/backend/handlers/payments/giftcards.go`
-**Lines:** ~860-1044
-
-**Prompt:** (delegate to `unspecified-high` agent)
-```
-TASK: Fix BuyGiftCard handler in giftcards.go so the Square API call happens AFTER the DB transaction commit.
-
-EXPECTED OUTCOME: Square CreatePayment is called only after DB transaction commits successfully. If commit fails, Square is never called.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- Read lines 840-1060 of giftcards.go
-- Restructure: start tx → INSERT gift_card with status='pending' → INSERT payment with status='pending' → tx.Commit() → squareClient.CreatePayment() → if success, UPDATE gift_card SET status='active' AND UPDATE payment SET status='completed'
-- Remove the Square API calls from before the tx (lines ~860-906)
-- Keep the idempotency check at lines ~846-855
-- The gift_cards table uses the same payment_status ENUM pattern
-
-MUST NOT DO:
-- Do not change the handler signature
-- Do not change gift card value calculations or VAT
-- Do not modify the CreateCardOnFileRaw call (it's for card tokenization, not charging)
-
-CONTEXT: Currently Square CreatePayment is called at line ~901 before the tx starts at line ~908. Schema supports pending→completed pattern.
-```
-
-### Step 3 — Code Review for 4b
-Read the changed handler. Verify the pending→completed pattern is correct.
-`go build ./handlers/payments/` passes.
-
-### Step 5 — Add backend tests for 4b
-Delegate to subagent following existing gift card test patterns.
-
-### Step 7 — Verify tests for 4b
-`go test ./handlers/payments/ -run TestBuyGiftCard -v -count=1` passes.
-
----
-
-### Step 1 — Fix Issue 4c: CreateTillSale saved_card/online_square inside-tx
-
-**File:** `/home/popertots/Crussell/backend/handlers/payments/till.go`
-**Lines:** ~112-420
-
-**Prompt:** (delegate to `unspecified-high` agent)
-```
-TASK: Fix CreateTillSale handler in till.go so Square API calls for saved_card and online_square payment methods happen AFTER the DB transaction commit.
-
-EXPECTED OUTCOME: Square CreatePayment for saved_card and online_square methods is called only after tx.Commit(). If commit fails, Square is never called. Card_machine, cash, and on_the_house methods remain unchanged.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- Read till.go lines 100-430 fully
-- Understand the 4 payment methods: cash (no Square), card_machine (checkout only — keep as is), saved_card (Square Payment), online_square (Square card tokenize + Payment), on_the_house (no Square)
-- For saved_card and online_square:
- - Insert the till_sale INSIDE the tx with status='pending' (saleStatus = "pending")
- - Insert/update gift_card records INSIDE the tx as currently done
- - Commit the tx
- - THEN call SquareClient.CreatePayment()
- - On Square success, update the till_sale status to 'completed' with the square_payment_id
- - On Square failure, leave till_sale as 'pending' for manual retry
-- For card_machine: keep the existing pattern (checkout created inside tx, reconciled by GetTillCheckoutStatus)
-- For cash/on_the_house: keep as-is (no Square call)
-
-MUST NOT DO:
-- Do not change the function signature or request/response types
-- Do not change the VAT calculation logic
-- Do not modify card_machine, cash, or on_the_house flows
-- Do not remove the gift card creation/topup logic from inside the tx (it should stay there)
-
-CONTEXT: The till_sales table has a `status payment_status DEFAULT 'pending'` column. The gift_card_transactions table tracks gift card operations. The existing card_machine flow already uses saleStatus = "pending".
-```
-
-### Step 3 — Code Review for 4c
-Read the changed handler. Verify:
-- saved_card flow uses pending→commit→Square→completed
-- online_square flow uses pending→commit→Square→completed
-- card_machine flow unchanged
-- cash/on_the_house flows unchanged
-- `go build ./handlers/payments/` passes
-
-### Step 5 — Add backend tests for 4c
-Delegate to subagent to add tests for the till sale flows.
-
-### Step 7 — Verify tests for 4c
-`go test ./handlers/payments/ -run TestCreateTillSale -v -count=1` passes.
-
----
-
-## Issue 5: Migrate all `time.Now()` → `clock.Now()` in test files
-
-**Strategy:** Process by Go package directory, one directory at a time.
-
-**Order:**
-1. `handlers/today/` (already clean — 0 replacements, just verify)
-2. `handlers/admin/` (4 files, ~67 replacements)
-3. `handlers/scheduling/` (2 files, ~54 replacements)
-4. `handlers/payments/` (4 files, ~25 replacements)
-5. `handlers/auth/` + `auth/` (2 files, ~12 replacements)
-6. `handlers/user/` (1 file, ~5 replacements)
-7. `handlers/notifications/` (1 file, ~4 replacements)
-8. `mw/` (1 file, ~4 replacements)
-9. `handlers/bookings/` (7 files, ~232 replacements — save the largest for last)
-
-**For EACH directory:**
-
-### Step 1 — Implement migrate
-Delegate to an `unspecified-high` agent.
-
-**Prompt template:**
-```
-TASK: Replace all time.Now() calls with clock.Now() in test files in /home/popertots/Crussell/backend/handlers/[PACKAGE]/.
-
-EXPECTED OUTCOME: Every test file in the package uses clock.Now() instead of time.Now(). Import of "crussell/clock" is added where missing. Build passes.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- For EACH *_test.go file in the directory:
- 1. Read the file to find all time.Now() calls
- 2. If the file doesn't already import "crussell/clock", add it to the imports
- 3. Replace EVERY occurrence of time.Now() with clock.Now()
- 4. Handle special cases:
- - time.Now().UTC() → clock.Now() (clock.Now() already returns UTC)
- - time.Now().In(londonLocation) → clock.Now().In(londonLocation) (time.Now().In() → clock.Now().In())
- - time.Now().Add(...) → clock.Now().Add(...) (bulk replace works)
- - time.Now().UnixNano() → clock.Now().UnixNano() (same)
- - time.Now().Truncate(...) → clock.Now().Truncate(...) (same)
- 5. Save each file after editing
-- Run go build ./handlers/[PACKAGE]/... to verify it compiles
-
-SPECIAL CASE for handlers/bookings/bookings_test.go:
-- The nextWorkingHour() helper function at ~line 48 uses time.Now() with .Hour() for wall-clock logic
-- Fix: replace time.Now() with clock.Now().In(londonLocation) so the wall-clock check uses London time
-- Then change the return statement to use .UTC() before returning
-
-MUST NOT DO:
-- Do not modify production code files (only *_test.go files)
-- Do not modify files outside the specified directory
-- Do not add "crussell/clock" to production code files
-- Do not remove any existing imports
-
-CONTEXT: clock.Now() returns time.Now().UTC(). All production code already uses clock.Now(). The test files were left behind in the previous migration.
-```
-
-### Step 3 — Verify
-Check each changed file:
-- Import of "crussell/clock" is present
-- `time.Now()` is fully replaced (no lingering occurrences)
-- `go build ./handlers/[PACKAGE]/...` passes
-- `go vet ./handlers/[PACKAGE]/...` passes
-
-### Step 5 — Add backend tests (not needed for this issue — it IS test migration)
-
-### Step 9 — Move to next package directory
-
----
-
-## Issue 6: Add autumn DST regression tests
-
-### Step 1 — Add tests (one file at a time)
-
-**Test file 1:** `/home/popertots/Crussell/backend/handlers/scheduling/scheduling_test.go`
-
-**Tests to add:**
-- `TestScheduling_DST_AutumnBack_BookingAt0130BST` — booking at 01:30 BST (00:30 UTC) on Oct 25 spans transition
-- `TestScheduling_DST_AutumnBack_BookingAt0130GMT` — booking at 01:30 GMT (01:30 UTC) on Oct 25
-
-Delegate to subagent:
-```
-TASK: Add autumn DST regression tests to scheduling_test.go.
-
-EXPECTED OUTCOME: Two new test functions covering bookings at the repeated hour on autumn DST day.
-
-REQUIRED TOOLS: Read, Edit
-
-MUST DO:
-- Read the existing test TestScheduling_DST_BlockerOnSpringForward (~line 2580) for pattern reference
-- Read scheduling_test.go helpers: resetTestData, makeAdminAvailableHoursRequest, findDayByDate, slotExists
-- Add TestScheduling_DST_AutumnBack_BookingAt0130BST:
- - Date: 2026-10-25 (Oct 25, 2026 = last Sunday of October, BST→GMT transition)
- - Create exceptional hours for Sunday (weekday 6) 09:00-17:00
- - Create a booking at 01:30 BST = 00:30 UTC (spans DST transition)
- - Query available hours for 2026-10-25
- - Assert: the 00:30 slot is blocked (booking starts)
-- Add TestScheduling_DST_AutumnBack_BookingAt0130GMT:
- - Same setup, but booking at 01:30 GMT = 01:30 UTC
- - Query available hours
- - Assert: the 01:30 slot is blocked
-
-MUST NOT DO:
-- Do not modify production code
-- Do not modify existing tests
-- Use time.Date with londonLocation for explicit timezone construction
-
-CONTEXT: The londonLocation variable is available in scheduling_test.go (package-level). Oct 25, 2026 is the autumn DST date. BST ends at 02:00 BST → 01:00 GMT. The duplicated hour is 01:00-02:00 wall-clock time (occurs twice: first as BST, then as GMT).
-```
-
-### Step 3 — Verify
-Read the added test functions. `go build ./handlers/scheduling/` passes.
-
-### Step 5 — Add more tests (next file)
-
-**Test file 2:** `/home/popertots/Crussell/backend/handlers/today/today_test.go`
-
-**Test to add:**
-- `TestFindWeekSummaryRange_AutumnDST` — London-aligned boundaries for Oct 25
-
-**Test file 3:** `/home/popertots/Crussell/backend/handlers/bookings/admin_reserve_test.go`
-
-**Test to add:**
-- `TestAdminReserveSlot_AutumnDST_ClosingBoundary` — closing time on autumn DST day
-
-**Test file 4:** `/home/popertots/Crussell/backend/handlers/admin/bookings_test.go`
-
-**Test to add:**
-- `TestAdminBookings_CountMatchesData_AutumnDST` — count query at autumn boundary
-
-Each gets its own subagent delegation, implemented and verified sequentially.
-
----
-
-## Issue 7: Align `timeSlots.ts` buffer to 60 minutes
-
-**File:** `/home/popertots/Crussell/frontend/src/lib/utils/timeSlots.ts`
-**Line:** ~201
-
-### Step 1 — Implement fix
-Simple one-line change. Can do directly or delegate.
-
-**Fix:** Change `Math.ceil((currentMinutes + 15) / 15) * 15` to `Math.ceil((currentMinutes + 60) / 15) * 15`.
-
-### Step 3 — Verify
-Read the changed line. Confirm the correct line was changed.
-
-### Step 5 — Add backend tests (skip — frontend-only)
-
----
-
-## Completion Verification
-
-After all 7 issues are complete:
-1. `cd /home/popertots/Crussell/backend && go build ./...` — must pass with zero errors
-2. `cd /home/popertots/Crussell/backend && go vet ./...` — must pass with zero errors
-3. `cd /home/popertots/Crussell/backend && go test ./...` — must pass (or note pre-existing failures)
-4. Report on each issue: completed, any deviations, any test failures
diff --git a/frontend/src/lib/components/admin/BusinessSettings.svelte b/frontend/src/lib/components/admin/BusinessSettings.svelte
index 98afc76..16ad2ca 100644
--- a/frontend/src/lib/components/admin/BusinessSettings.svelte
+++ b/frontend/src/lib/components/admin/BusinessSettings.svelte
@@ -336,7 +336,7 @@
{settings.website_url}
{:else}
- {'\u2014'}
+ —
{/if}
diff --git a/frontend/src/lib/components/admin/TimeBlockers.svelte b/frontend/src/lib/components/admin/TimeBlockers.svelte
index 9cb8c02..3cbb33a 100644
--- a/frontend/src/lib/components/admin/TimeBlockers.svelte
+++ b/frontend/src/lib/components/admin/TimeBlockers.svelte
@@ -877,10 +877,10 @@
minute: '2-digit',
hour12: true
})}}
- {' · '}
+ ·
{formatDuration(booking.duration_minutes)}
{#if booking.services?.length}
- {' · '}
+ ·
{booking.services.join(', ')}
{/if}
diff --git a/frontend/src/lib/components/booking/SelectedTimeSummary.svelte b/frontend/src/lib/components/booking/SelectedTimeSummary.svelte
index 3513ea7..20fefdd 100644
--- a/frontend/src/lib/components/booking/SelectedTimeSummary.svelte
+++ b/frontend/src/lib/components/booking/SelectedTimeSummary.svelte
@@ -96,8 +96,8 @@
{selectedDate} at {selectedTime}
- {' — '}
+ —
{endTime}
- {' ('}{duration} min)
+ ({duration} min)
diff --git a/frontend/src/lib/components/today/TodayCalendar.svelte b/frontend/src/lib/components/today/TodayCalendar.svelte
index 9fd1424..49a9117 100644
--- a/frontend/src/lib/components/today/TodayCalendar.svelte
+++ b/frontend/src/lib/components/today/TodayCalendar.svelte
@@ -1034,9 +1034,9 @@
minute: '2-digit',
hour12: true
})}
- {' · '}{formatDuration(booking.duration_minutes)}
+ · {formatDuration(booking.duration_minutes)}
{#if booking.services?.length}
- {' · '}{booking.services.join(', ')}{/if}
+ · {booking.services.join(', ')}{/if}