Unified form for personal and group. Group-only fields hide/show with animation based on type toggle.
Sport: first from profile. Location: first address. Type: Personal. Payment: Cash. Max: 10, Min: 3. Days: current weekday. Date: today. Time: NOT prefilled.
Save always active. On tap Save: validates name, duration, price, time. Red border + red label on empty fields (all at once). Auto-scroll to first error. Snackbar: "Please fill in required fields" for 2.5s. Error styles auto-clear after 3s so user can focus and retype. Re-typing in a field does NOT auto-clear — only Save re-validates.
Changing duration or date after time was selected → resets time to "Select time" + neutral snackbar: "Time slot reset — duration changed". Prevents invalid slot selection.
Accordion: hours with busy labels + "2/4" partial badges. Tap hour → expand minute chips. Busy minutes greyed. Confirm button shows selected range.
Auto-generates events 2 months ahead. Daily rolling check.
Calendar grid in bottom sheet. Past dates greyed. "Special" badge on profile.
Tap Location field → pushes to Training Locations screen in select mode. Shows all in-person + online locations. Tap = select + pop back. Supports both physical gyms and online meeting links.
POST /coach/training-sessions/ (create) · PUT /coach/training-sessions/{id}/ (edit).
All templates. Tap a card → edit (same form, prefilled).
Coach has 0 templates (fresh signup or all deleted). Illustration + title + subtitle + primary "Create session" CTA. CTA pushes to s-create. Bottom navbar still visible.
Shown during GET /coach/training-sessions/. Skeleton cards (3 placeholders) with shimmer. Prevents layout shift when data arrives.
Network/server error → inline retry card replaces list (cross-cutting pattern, #8).
GET /coach/training-sessions/ — returns template list.DELETE /coach/training-sessions/{id}/ — with cascade warning if used in scheduled events.
All templates. Pencil icon → edit (same form, prefilled).
Three location types + two usage modes.
Settings mode: tap card → edit details
Select mode: tap → select + pop back
Travel buffer: added before AND after each home visit session. Backend uses blocked_start = event_start - buffer, blocked_end = event_end + buffer for overlap checks.
Search-first approach with smart suggestions.
When user types → "Gyms Near You" replaced by autocomplete results.
Fullscreen map with two modes. Tap the pill to toggle.
Why locked by default: prevents accidental pin movement when user opens from Location Details to verify position.
Used for both create and edit location.
Create vs Edit: same screen. Edit prefills all fields + shows trash icon. Create hides trash icon.
Settings for home visit training type.
Backend: location_type='home_visit', travel_buffer_minutes, service_area_km (optional). At booking: athlete's address + distance check against service area.
Form for virtual meeting location.
Backend needed: location_type enum + meeting_link field. Make lat/lon optional.
Coach: Profile, Coaching, Payments, Account.
Connected: account cards with details. Zero: provider cards with Connect CTAs.
Tap "+" → system Google Sign-In → OAuth → backend stores tokens → auto-creates "321 Fit" calendar → loading → calendars fetched.
Tap Connect → Apple ID + app-specific password form. Backend validates → stores encrypted → fetches calendars.
Bidirectional. External events anonymized. Webhook + 15-min fallback.
Dedicated screen per account. 4 states via toggle.
CalDAV connection. Apple ID + app-specific password.
Apple-specific error states differ from Google.
Edit profile from Settings.
pd-avatar-sheet with 3 actions: Take photo / Choose from library / Remove photo (Medium destructive, tinted red 15%). Remove is hidden when no photo is set (initials-only).inputmode="decimal" (weight) / numeric" (height) for mobile keypad.currentYear − 13; strict ≥ 13y validation on Set — inline red banner in sheet on fail. Prod = UIDatePicker .compact (iOS) / MaterialDatePicker (Android)Full-screen text editor. Cancel/Save header. Reusable for About Me and Notes fields.
Push screen opened from Sport types set-card in Settings. Closed list of 33 sports across 8 sections — no custom sports (discovery, search, filter, matching all depend on canonical sport ID).
.sp-card.selected only — teal border + gradient; no hardcoded tick SVGs per card (CSS-driven from class)Recovery & therapy section exists because home-visit location type makes massage/physio a significant offering. Deferred from v1: Surfing / Kitesurfing / SUP / Equestrian — added on geo expansion.
Icons = draft adapted Tabler/Material style. Formal 33-icon set goes to design-tokens/assets/icons/sport/ as Phase 3 task (stable IDs, Style Dictionary pipeline).
Full-screen push from Personal Data. Multi-select — tap toggles row, Save commits all changes.
native · English (e.g. "Polski · Polish") — users search in eitherDiscard changes? sheet (Discard = Medium destructive tinted red, Keep editing = text)Prototype = 30 common languages. Production pulls full BCP-47 list.
Full-screen push from Personal Data. Same pattern as Time Zone — single-select, auto-dismiss.
Prototype uses ~35 countries covering major markets. Production pulls full ISO 3166-1 list.
Full-screen push from Personal Data. Single-select — tap row auto-dismisses back to Personal Data with the new value.
cal-select-row — 56px, teal accent on .selected, trailing checkmark toggled by class (no hardcoded SVG state)Prototype uses ~20 representative TZs. Production pulls full IANA zone list from device.
Coach's weekly schedule — when athletes can book training sessions. Applies indefinitely until edited. New accounts are seeded with Mon–Sun 07:00–22:00 so the screen opens populated rather than empty.
start · to · end) for split schedule (morning/evening with a break)Save availability (primary brand filled — follows feedback_footer_patterns.md, not a header icon)Validation (live per-interval):
End ≤ start — inline error + red outline on both inputsInterval < 30 min — inline error + red outlineIntervals overlap on same day — red outline + "Overlaps with another interval"Non-blocking warning banners:
No active days — amber banner, Save still allowed (temp "vacation")Existing bookings outside new hours — info banner + "View sessions" → bottom sheet list. Bookings stay; only new requests are affected.Time picker (platform-native in production):
UIDatePicker with .compact style — tap chip expands inline wheelsMaterialTimePicker dialog, dial modeFlow states:
Loading — 5 skeleton rows mimic day layout; footer hidden during fetchSaving — Save button shows inline spinner + cursor:wait; content still interactive (prevents accidental edit)Save error — red banner above footer with inline Retry; Save stays tappable as secondary retrySaved — snackbar "Availability updated" (1.4s) + resets to defaultBack button behavior: opens Discard changes? bottom sheet. Discard = Medium destructive (tinted red), Keep editing = text button. In production, the sheet only opens when there are unsaved changes.
HIG / a11y / theming:
min-height: 44px for tap target (checkbox + label)::before hit-area expansion to 44×44 effectiveScope decisions (v1): single availability for all locations · no copy-to-other-days · no quick presets · no vacation mode — see project_pending_spec_updates.md for deferred items.