Architecture & Screen Specification

DCCWeeklyActivities

A comprehensive screen-by-screen specification covering every user interface, data element, interaction pattern, and technical architecture decision for the Desi Cycling Club iOS application. Built on SwiftUI + Cloudflare Workers + Strava API v3. Approach A: Club code auth with serverless backend.

55+
Features Shipped
3
Platforms (iOS, tvOS, PWA)
5
Security Layers
22K
Lines of Swift
01 — System Architecture
How It's Built
A layered architecture across UI, business logic, services, and data. iOS native SwiftUI with Cloudflare Worker backend. Approach A: club code auth, no client-side OAuth. 80 Swift files, ~22K lines.
Layered Architecture — Top to Bottom
📱
iOS / iPadOS
Primary Platform
📺
Apple TV / tvOS
Big Screen Display
PWA (Android/Web)
SCRUM-53 → In Progress
Presentation Layer — SwiftUI Views
RootView
ClubCodeLoginView
MemberStatsChartView
WeeklyReportTableView
InsightsView
TVViews
Business Logic Layer
Data Aggregation
Group by member, totals, averages
Trend Calculation
Week-over-week comparisons
Sorting & Filtering
Multi-metric sort, date filters
Insights Engine
Performance comparisons, coaching
🚴 CloudDataFetcher.swift
Async fetch from Cloudflare Worker /club-data endpoint, JSON parsing, week boundary tracking, Activity conversion
🔐 BiometricAuth.swift
Face ID / Touch ID via LocalAuthentication. Lazy LAContext to avoid XPC 501. Session persistence via isAuthenticated state
📦 WeeklyCache.swift
Disk cache with 14-day TTL, MemberSnapshot persistence, previous-week lookup for trend calculation
File Organisation
FilePurposeLayer
RootView.swiftAuth gating, launch animation, navigation (2,448 lines)UI
MemberStatsChartView.swiftSwift Charts: radar, scatter, bar charts (806 lines)UI
WeeklyReportTableView.swiftWeekly report with trend arrows (403 lines)UI
TVRootView.swifttvOS navigation root + 4 tab views (943 lines)UI
Activity.swiftActivity model: distance, speed, elevation, type (44 lines)Model
MemberStats.swiftPer-member stats with TrendDirection enum (64 lines)Model
CloudDataFetcher.swiftCloudflare Worker data fetch service (137 lines)Service
BiometricAuth.swiftFace ID / Touch ID biometric gate (361 lines)Service
WeeklyCache.swiftDisk cache with MemberSnapshot (146 lines)Service
State Management Pattern
@State var clubAuth = ClubCodeAuthService.shared
@State var biometric = BiometricAuth.shared
@State var cloudData = CloudDataFetcher.shared
@State var stats: [MemberStats] = []
@State var activities: [Activity] = []
@State var athleteProfile: AthleteProfile?
@State var isLoading: Bool = false
@State var selectedWeekOffset: Int = 0
💡
WWDC 2025 Upgrade
Already migrated to @Observable macro (Swift 6). All singletons use @Observable + @MainActor pattern. Eliminates unnecessary redraws. ClubCodeAuthService.shared, CloudDataFetcher.shared, BiometricAuth.shared, FeatureRequestService.shared all follow this pattern.
02 — Data Flow
How Data Travels
From Strava's servers through Cloudflare Workers to every pixel on screen. Approach A: Cloudflare Worker fetches Strava data server-side, aggregates per-member stats, caches in KV, and serves JSON to the iOS app via /club-data endpoint.
Authentication Flow
1
App Launch
Check Keychain for saved token
2
Biometric Gate
Face ID / Touch ID challenge
↓ (success)
3
Token Valid?
Check expiry, refresh if needed
4
Auto Fetch
Load club activities immediately
Data Fetch Pipeline
① Rate Limit Check
100/15min · 1000/day — throttle if needed
② Network Check
Online → fetch · Offline → load cache
③ GET /clubs/{id}/activities
URLSession async/await · TLS 1.3
④ JSON Decode
Codable → [Activity] · validate
⑤ Cache Locally
JSON persist · timestamp · TTL
⑥ Aggregate Stats
Group → calculate → sort → render
Statistics Aggregation
Input: [Activity]
• athlete.firstname + lastname
• distance (metres)
• moving_time (seconds)
• total_elevation_gain
• average_speed (m/s)
• type (Ride/Run/Walk)
↓ Group by member
Calculated Metrics
• totalDistance (km)
• totalRides (count)
• avgSpeed (km/h)
• elevationGain (m)
• trend (▲▼ vs prev week)
• rankPosition (#1, #2...)
↓ Sort by selected metric
Output: [MemberStats]
Ready to render in Charts, Table, Insights views
Error Handling Strategy — 4 Layer Defence
L1 Network Layer
  • → URLError handling
  • → HTTP status codes (4xx/5xx)
  • → Timeout detection
  • → Rate limit 429 detection
L2 API Response
  • → JSON parsing errors
  • → Missing field handling
  • → Invalid format detection
  • → Empty response handling
L3 Business Logic
  • → Validation errors
  • → Calculation edge cases
  • → State inconsistencies
  • → Date range conflicts
L4 Presentation
  • → User-friendly messages
  • → Recovery suggestions
  • → Retry mechanisms
  • → Graceful degradation
03 — User Screen: Launch & Biometric Lock
First Impression
The very first thing a user sees. If a token exists in Keychain, biometric lock appears. If not, redirect to login. This screen sets the tone — professional, secure, instant.
9:41
▮▮▮ ✦ 🔋
DCC Weekly
👤
Unlock to Continue Use Face ID to access your club data
Use Face ID 🔒
Or enter passcode
Launch / Biometric Lock
What The User Sees — Element Breakdown
1
DCC Weekly Brand Mark
App name in Strava orange, monospace typeface. Establishes identity immediately before any interaction.
Position: Top center · Font: JetBrains Mono · Color: #FC4C02
2
User Avatar / Profile Icon
Large circular avatar icon (56×56pt). Shows the member's Strava profile photo if cached, or generic user icon on first launch.
Source: Strava athlete profile · Fallback: SF Symbol person.circle
3
Unlock Prompt
"Unlock to Continue" heading + explanatory sub-text. Triggers Face ID / Touch ID via LocalAuthentication framework automatically on appearance.
Auto-trigger on viewDidAppear · reason string shown in system Face ID dialog
4
Use Face ID Button
Manual trigger for users who dismissed auto-prompt. Rounded rectangle, subtle border, system icon.
Fallback: "Enter Passcode" text link below
Decision Logic
Token in Keychain?
✓ YES → Show biometric lock
✗ NO → Skip, show login screen

Auth Result?
✓ Success → Fetch data, show main
✗ Failed → Keep lock shown, show retry
✗ Cancelled → Keep lock, allow retry
UX Quality Notes
✅ Auto-triggers on appear
✅ Keychain not UserDefaults
✅ Graceful fallback to PIN
Add haptic on auth success
Animated transition to main
Dark/light gradient bg
04 — User Screen: Login & Strava OAuth
Connect with Strava
First-time user experience. A photo carousel of club rides creates emotional connection before any tap. OAuth opens in Safari for maximum trust signal.
9:41
▮▮▮ ✦ 🔋
DCC Weekly
Activities
Desi Cycling Club
Track weekly performance, compare with clubmates, celebrate your best rides
🚴 Connect with Strava
Opens Strava to authorise access to your club's activity data
Login Screen
Screen Elements — Complete Breakdown
1
Club Photo Carousel
Auto-scrolling horizontal carousel of club ride photos. Creates emotional connection and communicates the app's purpose before any reading. Photos sourced from Strava club media or bundled assets.
Auto-scroll: 3s interval · Swipe gesture supported · Crossfade transition
2
App Branding Block
"DCC Weekly Activities" title + club name subtitle. Playfair Display serif for gravitas. Sets the premium tone.
Title: Playfair Display 24pt 900w · Sub: JetBrains Mono 11pt
3
Value Proposition Card
One-sentence benefit statement: "Track weekly performance, compare with clubmates, celebrate your best rides." Answers "why should I authorise this?" before the button.
Max 2 lines · Centered · Subtle bg card
4
Connect with Strava Button
Strava-branded CTA button in signature orange. Tapping opens Strava OAuth in Safari (SFSafariViewController) — system browser signals maximum trustworthiness to user.
URL: GET /oauth/mobile/authorize · scope: activity:read · redirect: dcc-activities://
5
Consent Explainer
Small text explaining what access is being requested. Trust-building transparency — users authorise more readily when they know exactly what's shared.
Text: "Club activity data only · No personal routes shared"
OAuth Flow Sequence
Tap Button
Open Safari
User Authorises
Redirect to dcc-activities://
Exchange code for token
Save to Keychain → Main View
05 — User Screen: Charts View
The Visual Story
The primary data visualisation screen. Bar charts, pie charts, summary cards, and a ranked leaderboard — all for the selected metric. This is where the "wow factor" lives.
9:41
▮▮▮ ✦ 🔋
Club Stats
Week of 24–30 Mar 2026
Charts
Table
Activity
Total km
847
↑ 12% vs last wk
Rides
23
12 members active
📊
Charts
📋
Table
🚴
Rides
Insights
Charts — Bar View
9:41
▮▮▮ ✦ 🔋
Top Riders
Distance ▾
🥇
S
Sarah M.
124km
🥈
J
James K.
118km
🥉
R
Roisin F.
98km
4.
T
Tom B.
87km
5.
A
Aoife D.
72km
📊
Charts
📋
Table
🚴
Rides
Insights
Charts — Rankings List
All Data & Elements on Charts Screen
A
Tricolor Header Bar
3px French tricolor stripe (🇫🇷 blue/white/red) at the very top. Club identity marker visible on every screen. Date range shown below: "Week of [Mon DD–Sun DD MMM YYYY]".
Date range calculated server-side from activity timestamps
B
Summary Cards (2×2 grid)
Four metric summary cards: Total Distance (km), Total Rides (count), Active Members (count), Club Elevation (m). Each shows week-over-week trend % with directional arrow.
Calculated from aggregated MemberStats · Trend = (thisWeek - lastWeek) / lastWeek × 100
C
Metric Picker
Segmented control or picker to switch between: Distance · Rides · Speed · Elevation. Changing metric updates bar chart, pie chart, and rankings instantly.
Default: Distance · Animated transition on change · State persisted in ViewMode
D
Bar Chart (Swift Charts)
Vertical bar chart with one bar per member, sorted by selected metric. Bars are labelled with first name (abbreviated). Top performer bar highlighted in Strava orange, others in teal. Tapping a bar reveals a tooltip with full name + value.
Framework: Swift Charts (iOS 16+) · Animation: spring() on data change · Tap: show popover
E
Pie Chart (Swift Charts)
Donut/pie chart showing each member's share of the club total for the selected metric. Colour-coded segments. Accessible with value labels. Shows "total club: X km" in the centre.
SectorMark API · Accessible labels · Legend below chart
F
Rankings List
Scrollable leaderboard with rank number, member avatar (initials or Strava photo), name, metric value, and trend arrow (▲ up / ▼ down / — flat vs previous week). Medal emojis for top 3.
Avatar: Strava profile photo or initials circle · Trend: compare thisWeek vs lastWeek same metric
View Mode Switcher
Tab bar at bottom with 4 tabs:
📊 Charts — This screen
📋 Table — Sortable data grid
🚴 Rides — Activity list
Insights — AI analysis
WOW Factor Upgrades
→ Animated bar entry on load
→ Haptic on top rider tap
→ Mesh gradient background
→ Confetti on personal best
→ 120Hz ProMotion animations
06 — User Screen: Table View
The Numbers
Dense data display for members who want precision. Every metric for every rider in one sortable grid. The analyst's view — sort by any column, instant reorder.
9:41
▮▮▮ ✦ 🔋
Weekly Table
24–30 March 2026
#
Rider
km↓
Rds
Ele
Trnd
1
Sarah M
124
4
1.2k
▲8%
2
James K
118
3
980
▼3%
3
Roisin F
98
3
760
▲12%
4
Tom B.
87
2
540
📊
Charts
📋
Table
🚴
Rides
Insights
Table View
Table Columns — All Data Displayed
ColumnDataSourceSortable
# RankPosition 1–N by selected sortComputed from sort ordern/a — relative
RiderFirst + Last name (abbreviated)athlete.firstname + lastnameA→Z
km DistanceTotal km ridden that weekΣ distance / 1000✓ Default desc
RidesNumber of activitiesactivities.count
ElevationTotal metres climbedΣ total_elevation_gain
Avg SpeedAverage km/h across all ridesavg(average_speed × 3.6)
Trend▲▼ % change vs prev weekDual-week fetch comparison
Sort Interaction
Tap any column header → sort by that metric. Tap again → reverse sort. Active sort column highlighted in Strava orange with ↑↓ indicator. Animated row reorder with spring() transition.
Highlight Logic
Top performer row: Strava orange bg tint.
Trend ▲: green text
Trend ▼: red text
Trend –: grey text
Current user's row: blue tint (self-identification)
07 — User Screen: Activities List
Every Ride
Individual activity cards for all club members that week. The granular view — see every ride, who did it, when, how far, and how hard. Filter by member, sort by date.
9:41
▮▮▮ ✦ 🔋
This Week's Rides
23 activities · 12 members
S
Sarah M.
Mon 24 Mar · 7:32am
🚴 Ride
Dist
42.3km
Speed
28.4k/h
Elev
420m
J
James K.
Tue 25 Mar · 6:15am
🚴 Ride
Dist
38.1km
Speed
30.2k/h
Elev
280m
R
Roisin F.
Wed 26 Mar · 8:00am
🏃 Run
Dist
12.4km
Pace
5:12/km
Elev
85m
📊
Charts
📋
Table
🚴
Rides
Insights
Activities List
ActivityRow — Data Per Card
ElementDataFormat
Member AvatarStrava profile photo or initials circle20×20pt circle, colored by name hash
Member Nameathlete.firstname + lastname[0]."Sarah M." format for privacy
Date & TimeActivity start datetime"Mon 24 Mar · 7:32am"
Activity Typetype: Ride / Run / Walk / SwimEmoji icon + label badge
Distancedistance metres → km"42.3km" (1dp)
Speed / Paceaverage_speed m/s → km/h (Ride) or min/km (Run)"28.4km/h" or "5:12/km"
Elevationtotal_elevation_gain metres"420m" · ">1000" shown as "1.2k"
Moving Timemoving_time seconds → H:MM"1h 28m" format
⚠️
Known API Constraint
Strava's /clubs/{id}/activities endpoint does NOT return date fields in its response. Date filtering must be handled server-side or by fetching additional pages and filtering by position. This requires careful pagination logic.
08 — User Screen: Insights Engine
The Intelligence
The most sophisticated screen — three analysis modes transforming raw data into narrative coaching intelligence. This is the differentiator. No other club app does this.
9:41
▮▮▮ ✦ 🔋
✨ My Insights
My Stats
vs Top 3
Deep Dive
My Distance
87km
▲ 15% ↑
My Rank
#4
of 12 active
🎉 Personal Best!
Your highest weekly distance in 8 weeks. Keep this up and you'll crack the top 3 by month end.
💡 Coaching Tip
You're 37km behind #3 Roisin. One extra 40km Sunday ride would close that gap.
📊
Charts
📋
Table
🚴
Rides
Insights
Insights — My Stats
9:41
▮▮▮ ✦ 🔋
✨ Me vs Top 3
My Stats
vs Top 3
Deep Dive
Distance comparison
Sarah M. 🥇124km
James K. 🥈118km
Roisin F. 🥉98km
⟶ You87km
📊 Gap Analysis
11km behind 3rd place. Your avg speed (28.4 k/h) is actually faster than Roisin's. More rides = your path up.
📊
Charts
📋
Table
🚴
Rides
Insights
Insights — vs Top 3
Three Insights Modes — Full Spec
Mode 1 — Just My Stats
• My weekly distance (km)
• My ride count
• My avg speed (km/h)
• My elevation (m)
• My rank in club
• Week-over-week % change
• Personal best detection
• Streak tracking
Positive Reinforcement Cards: Personal best celebrations, streak badges, improvement callouts. Generates motivating copy automatically from data comparisons.
Coaching Tip: "You're X km from rank N — one extra ride this weekend would close it." Specific, actionable, data-driven.
Mode 2 — Me vs Top 3 Riders
Side-by-side visual comparison bars for each metric between the user and the week's top 3 performers. Shows gap in absolute values AND as percentage.
• Distance bars comparison
• Ride count comparison
• Speed comparison
• Elevation comparison
• Gap to #3 (closeable!)
• Strength identifier
• "Your advantage" callout
• Improvement trajectory
Mode 3 — Worst Performer & Why
The brutal-but-kind analysis mode. Identifies the week's lowest-performing member and provides constructive context — "low week due to only 1 ride" vs "slower speed across all rides". Generates empathetic, actionable coaching language. Never shaming — always constructive.
Selectable rider list → choose who to analyse → generates specific insights for that rider
09 — Platform: Apple TV / tvOS
The Big Screen
Apple TV turns DCC Weekly into a shared clubroom experience. Stats displayed on the big screen during post-ride coffee. The most impressive feature most members have never seen in a club app.
Apple TV Display — 1920×1080 Landscape
DCC Weekly Activities
Week of 24–30 March 2026 · 23 rides · 12 active members
DUNBOYNE CC
CLUB TOTAL DISTANCE
847km
↑ 12% vs last week
TOTAL ELEVATION
8,420m
WEEKLY LEADERBOARD — DISTANCE
🥇
Sarah Murphy
124 km
▲ 8%
🥈
James Kelly
118 km
▼ 3%
🥉
Roisin Farrell
98 km
▲ 12%
4.
Tom Brennan
87 km
TV Screen Content Specification
1
Persistent Header
Club name, week date range, total ride count, active member count. Tricolor bar at top.
2
Summary Stats Cards
Total club distance (km), total elevation, active members. Large numbers, high contrast. Designed for readability at 3 metres.
3
Full Name Leaderboard
TV shows full names (privacy appropriate in group setting). Medal emojis, colour-coded trend arrows. Designed for remote control navigation.
4
Tab Navigation
Stats Tab + Activities Tab. Navigated with Apple TV remote. Focus engine handles highlight states automatically.
tvOS Design Differences vs iOS
iOStvOS
Abbreviated names "Sarah M."Full names "Sarah Murphy"
Touch / gesture navigationRemote focus engine
Small phone-sized textLarge TV-readable typography
Tab bar at bottomTabView at top / side
Individual scrollStatic display layout
Biometric lockSimple login screen
Dense data tablesSparse, readable leaderboard
10 — Security Architecture
Fort Knox Standard
Four independent security layers. No credentials in UserDefaults. No plain text tokens. Biometric requirement means even a stolen phone can't access club data.
🌐
L1 — HTTPS / OAuth 2.0
All API communication over TLS 1.3. OAuth 2.0 for Strava authorisation. Token exchange happens server-to-server style. Deep link callback via custom scheme dcc-activities://. URLSession rejects invalid certificates automatically.
🗝
L2 — Keychain Storage
OAuth access token + refresh token stored in iOS Keychain via Security.framework. Keychain items encrypted with AES-256 hardware encryption. Marked kSecAttrAccessibleWhenUnlockedThisDeviceOnly — token never leaves the device, not synced to iCloud.
🧬
L3 — Biometric Auth
LocalAuthentication.framework. Face ID on Face ID devices, Touch ID on Touch ID devices. Auto-prompts on app foreground if token exists. Fallback to device passcode. Biometric data never leaves the Secure Enclave — the app only receives success/failure boolean.
What Data Is Accessed — Privacy Scope
✓ Data the app accesses
  • → Club member first/last names (anonymised in display)
  • → Activity type (Ride/Run/Walk)
  • → Distance, speed, elevation per activity
  • → Activity moving time
  • → Club membership roster
✗ Data the app never accesses
  • → GPS route data / privacy zones
  • → Heart rate / power data
  • → Personal messages or kudos
  • → Followers / following lists
  • → Financial information
11 — API Integration
Cloudflare Worker + APIs
Four endpoints, careful rate limit management, dual-week data strategy for trend calculation. The only data source — everything visual is derived from these JSON responses.
Endpoint Reference
StepMethodEndpointReturnsRate Impact
1. Auth GET /oauth/mobile/authorize Auth code → redirect 0 (user browser)
2. Token exchange POST /api/v3/oauth/token access_token, refresh_token, expires_at 1 req
3. Fetch activities (this week) GET /api/v3/clubs/{id}/activities [SummaryActivity] — no dates ⚠️ 1–5 req (pagination)
4. Fetch activities (last week) GET /api/v3/clubs/{id}/activities Previous week for trend calc 1–5 req (pagination)
5. Token refresh POST /api/v3/oauth/token New access_token 1 req (when expired)
Rate Limit Strategy
100 requests / 15 minutes
1,000 requests / day
429 Too Many Requests → backoff
Per data refresh: ~10 requests (pagination for two weeks). With 100 users, daily refresh = ~100 requests. Well within limits. App checks rate headers on each response and implements exponential backoff on 429.
⚠️ Critical API Constraint: No Date Fields
⚠️
Club Activities endpoint has no dates
The SummaryActivity objects returned by the club endpoint do NOT include start_date fields. Week separation must be done server-side via pagination counting or by fetching with per_page and page parameters and inferring from activity count patterns.
12 — State Management
@Observable Singletons
All app state lives in ContentView and flows down to child views via property injection. No global singletons, no shared mutable state between views.
App State — ContentView (current @StateObject)
// Services
@StateObject var stravaAPI: StravaAPI
@StateObject var biometricAuth: BiometricAuth

// Data
@State var activities: [ClubActivity] = []
@State var memberStats: [MemberStats] = []
@State var prevWeekStats: [MemberStats] = []

// UI State
@State var isLoading: Bool = false
@State var errorMessage: String? = nil
@State var viewMode: ViewMode = .charts
@State var selectedMetric: Metric = .distance
@State var dateRange: (Date, Date)? = nil
@State var isAuthenticated: Bool = false
WWDC 2025: Migrate to @Observable
// ✓ Modern Swift 6 approach
@Observable
class AppState {
  var activities: [ClubActivity] = []
  var memberStats: [MemberStats] = []
  var isLoading = false
  var viewMode: ViewMode = .charts
}
Benefits: Eliminates unnecessary redraws. Views only update when their specific observed properties change. Simpler syntax. Apple's 2025 recommended approach (WWDC session: "Migrate your app to Swift 6").
ViewMode Enum
enum ViewMode {
  case charts
  case table
  case activities
  case insights
}
13 — Performance & Accessibility
Zero Compromises
ProMotion 120Hz targets, lazy loading, proper accessibility. The one area that needs the most work before App Store submission is VoiceOver and Dynamic Type compliance.
Performance Optimisations
  • Async/await networking
    Non-blocking — UI never freezes during fetch
  • LazyVGrid summary cards
    Only renders visible cards — scroll is smooth
  • List for activities
    iOS native List recycling for long activity feeds
  • Computed sort caching
    Sort only recomputed when data or sort key changes
  • SF Symbols
    Vector icons — never blur at any resolution
  • SwiftUI Performance Instrument
    WWDC 2025: profile every view body update
Accessibility Audit — Priority Fixes
🚨
App Store Rejection Risk
Apple's App Review checks accessibility. Inaccessible apps risk rejection.
  • VoiceOver labels on all interactive elements and chart data
  • Dynamic Type support — text must scale with system font size
  • Minimum touch targets — 44×44pt minimum (HIG requirement)
  • Color contrast — 4.5:1 minimum for normal text (WCAG AA)
  • Chart accessibility — Swift Charts needs .accessibilityLabel modifiers
Local Cache Strategy
Current: JSON + UserDefaults
• Activities array serialised to JSON
• Stored in UserDefaults with timestamp
• TTL: 15 minutes before stale
• Offline: serve cache indefinitely

Upgrade: SwiftData (iOS 17+)
• Proper relational storage
• Query by date range
• Historical season comparison
• Migration path from UserDefaults
• CloudKit sync potential (Phase 3)
14 — WWDC 2025 Upgrade Plan
Shipped & Planned
Apple's 2025 developer conference introduced transformative APIs. Adopting these early makes DCC Weekly look like a first-party Apple app and dramatically improves the experience.
🔥 HIGHEST IMPACT — Do These First
1. Liquid Glass Design System
Apple's iOS 26 visual language — translucent panels with depth-aware blur and dynamic material. Adopting it makes DCC look indistinguishable from a first-party Apple app. Cards would appear to float above the content. Available with a one-year grace period for developers.
2. SwiftUI Performance Instrument
New Xcode instrument with lanes for view body updates, lazy stack performance, and platform view updates. Profile the app at 120Hz on a ProMotion device. Target sub-16ms frame budget for every interaction.
3. @Observable Migration
Replace all @StateObject/@ObservableObject with @Observable macro (Swift 5.9+). Apple's recommended 2025 pattern. Eliminates unnecessary view redraws. Simpler code. Pair with @Previewable for dynamic SwiftUI previews.
✦ HIGH VALUE — Phase 2 Features
4. Live Activities — Dynamic Island
Show a club ride in progress on the Lock Screen and Dynamic Island. "Sarah M. just passed 100km this week 🎉" appearing on the Lock Screen of every club member is extraordinary theatre.
5. WidgetKit — Home Screen Widget
Weekly rank + distance visible without opening the app. The first thing a member sees on Monday morning. Small widget: rank + distance. Medium widget: top 3 mini-leaderboard.
6. Native WebView (SwiftUI 2025)
Finally available. Eliminates UIViewRepresentable workarounds for Strava OAuth browser. Cleaner, more testable authentication flow.
7. iPad Menu Bar (iPadOS 26)
Swipe-down menu bar + Commands API = DCC Weekly feels like a proper iPad app. Critical for cycling club members who view stats on iPad at home.
Appendix — Strategic Roadmap
The Journey
Three phases from MVP to elite performance platform. Each phase builds the habit loop — notifications, widgets, and social features that make the app indispensable.
Phase 1 · v1.0 · Now
Foundation
  • Strava OAuth integration
  • iOS + iPad + tvOS SwiftUI
  • Swift Charts visualisations
  • Sortable data table
  • Activities list view
  • Insights engine (3 modes)
  • Biometric auth + Keychain
  • Offline JSON cache
  • Dual-week trend calc
  • TestFlight beta
🎯 Goal: App Store submission Q2 2026
Phase 2 · v1.1–1.2 · Q3 2026
Habit Formation
  • Accessibility audit + fixes
  • SwiftData historical storage
  • WidgetKit home screen widget
  • Push notifications (APNs)
  • Live Activities + Dynamic Island
  • Celebration moments + haptics
  • watchOS companion app
  • @Observable migration
  • Season history view
  • Liquid Glass design refresh
🎯 Goal: Daily active users, viral word-of-mouth
Phase 3 · v2.0 · 2027
Elite Platform
  • Foundation Models API (on-device AI)
  • Natural language insights
  • Siri App Intents
  • Multi-club support (CloudKit)
  • Social features + kudos
  • CarPlay integration
  • visionOS spatial stats
  • Advanced analytics dashboard
  • Coach mode (admin tools)
  • App Store expansion (multi-club)
🎯 Goal: Platform for all cycling clubs worldwide
Senior Architect's Final Verdict
This architecture is production-ready and professionally structured. The separation of concerns, security model, and platform coverage are genuinely world-class for a club app. The Insights Engine is the standout differentiator — no comparable app in this space provides that level of narrative intelligence from raw activity data.

The foundation is excellent. The next priority is accessibility compliance (without it, App Store submission is at risk) and then SwiftData migration to unlock historical analytics.
From a UX perspective: the tricolor header, biometric security, and multi-platform support create an experience that genuinely surprises cyclists — they've never seen their club data presented at this level. The Apple TV support in particular is remarkable; seeing your club leaderboard on a big screen in a clubhouse is a genuine "wow moment".

Phase 2 Live Activities + WidgetKit will be the viral moment — the day members start seeing each other's weekly progress on their Lock Screens, the app becomes the club's primary communication tool.