# lzrant/picklediq architecture

> Architecture diagram of the GitHub repository lzrant/picklediq, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-10-01). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/lzrant/picklediq
- Repository: https://github.com/lzrant/picklediq

## Overview

PaddleIQ captures pickleball sessions on Apple Watch, classifies motion windows into shot events, and syncs match updates to the iPhone for history and analytics. The phone also supports manual match entry, paddle management, social/profile surfaces, and Firebase match sync. Shared models and services provide the domain layer. Firebase capabilities are documented, but sampled source does not establish all backend wiring; those relationships are marked unverified or omitted.

Main entry points: [`iOS/PickledApp/ViewModels/AppViewModel.swift`](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/ViewModels/AppViewModel.swift) (App State), [`iOS/PickledApp/Views/RootTabView.swift`](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/RootTabView.swift) (App Navigation), [`watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift`](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift) (Live Match State), [`watchOS/PickledWatchApp/Views/LiveMatchView.swift`](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Views/LiveMatchView.swift) (Live Match UI)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_watch["Watch Capture"]
  node_live_view["Live Match UI"]
  node_live_vm["Live Match State"]
  node_motion["Motion Capture"]
  node_workout["Workout Session"]
  node_watch_sender["Watch Sync Sender"]
end

subgraph group_phone["iPhone App"]
  node_phone_receiver["Phone Sync Receiver"]
  node_app_state["App State<br/>[AppViewModel.swift]"]
  node_dashboard["Analytics Dashboard"]
  node_match_history["Match History"]
  node_paddle_bag["Paddle Bag"]
  node_social["Social Feed<br/>[SocialView.swift]"]
  node_profile["Profile<br/>[ProfileView.swift]"]
  node_root_tabs["App Navigation<br/>[RootTabView.swift]"]
  node_firebase_client["Match Sync Client"]
end

subgraph group_shared["Shared Domain"]
  node_classifier["Shot Classifier"]
  node_sync_message["Sync Messages"]
  node_paddle_analytics["Paddle Analytics"]
  node_paddle_library["Paddle Library"]
  node_dupr["DUPR Abstraction<br/>[DUPRService.swift]"]
  node_match_model["Match Model<br/>[Match.swift]"]
  node_paddle_model["Paddle Models<br/>[Paddle.swift]"]
  node_shot_model["Shot Events<br/>[ShotEvent.swift]"]
  node_user_model["User Profile<br/>[UserProfile.swift]"]
end

subgraph group_cloud["Cloud Services"]
  node_firebase_functions["Cloud Functions<br/>[index.js]"]
  node_firestore[("Firestore<br/>[firestore.rules]")]
  node_dupr_service["DUPR Service"]
end

node_player(("Player"))

node_player -->|"controls"| node_live_view
node_live_view -->|"invokes"| node_live_vm
node_live_vm -->|"starts capture"| node_motion
node_motion -->|"classifies windows"| node_classifier
node_live_vm -->|"controls workout"| node_workout
node_live_vm -->|"sends events"| node_watch_sender
node_watch_sender -->|"encodes messages"| node_sync_message
node_phone_receiver -->|"decodes messages"| node_sync_message
node_app_state -->|"observes updates"| node_phone_receiver
node_app_state -->|"syncs matches"| node_firebase_client
node_firebase_client -->|"calls upsertMatch"| node_firebase_functions
node_firebase_functions -.->|"uses data store"| node_firestore
node_app_state -->|"manages matches"| node_match_model
node_app_state -->|"manages paddles"| node_paddle_model
node_app_state -->|"maintains profile"| node_user_model
node_app_state -->|"computes metrics"| node_paddle_analytics
node_app_state -->|"loads paddles"| node_paddle_library
node_root_tabs -->|"routes to"| node_dashboard
node_root_tabs -->|"routes to"| node_match_history
node_root_tabs -->|"routes to"| node_paddle_bag
node_root_tabs -->|"routes to"| node_social
node_root_tabs -->|"routes to"| node_profile
node_dashboard -->|"reads metrics"| node_app_state
node_match_history -->|"reads history"| node_app_state
node_paddle_bag -->|"manages bag"| node_app_state
node_social -->|"reads social data"| node_app_state
node_profile -->|"reads profile"| node_app_state
node_dupr -.->|"provides integration"| node_dupr_service

click node_live_view "https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Views/LiveMatchView.swift"
click node_live_vm "https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift"
click node_motion "https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Motion/MotionCapturePipeline.swift"
click node_classifier "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/ShotClassifier.swift"
click node_workout "https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/WorkoutSessionController.swift"
click node_watch_sender "https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/WatchConnectivitySender.swift"
click node_sync_message "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/SessionSyncMessage.swift"
click node_phone_receiver "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Services/PhoneConnectivityController.swift"
click node_app_state "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/ViewModels/AppViewModel.swift"
click node_dashboard "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/DashboardView.swift"
click node_match_history "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/MatchHistoryView.swift"
click node_paddle_bag "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/PaddleBagView.swift"
click node_social "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/SocialView.swift"
click node_profile "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/ProfileView.swift"
click node_root_tabs "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/RootTabView.swift"
click node_firebase_client "https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Services/FirebaseSyncClient.swift"
click node_paddle_analytics "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/PaddleAnalytics.swift"
click node_paddle_library "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/PaddleLibrary.swift"
click node_dupr "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/DUPRService.swift"
click node_match_model "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/Match.swift"
click node_paddle_model "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/Paddle.swift"
click node_shot_model "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/ShotEvent.swift"
click node_user_model "https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/UserProfile.swift"
click node_firebase_functions "https://github.com/lzrant/picklediq/blob/main/Firebase/functions/index.js"
click node_firestore "https://github.com/lzrant/picklediq/blob/main/Firebase/firestore.rules"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_live_view,node_live_vm,node_motion,node_workout,node_watch_sender toneBlue
class node_phone_receiver,node_app_state,node_dashboard,node_match_history,node_paddle_bag,node_social,node_profile,node_root_tabs,node_firebase_client toneAmber
class node_classifier,node_sync_message,node_paddle_analytics,node_paddle_library,node_dupr,node_match_model,node_paddle_model,node_shot_model,node_user_model toneMint
class node_firebase_functions,node_firestore,node_dupr_service toneRose
class node_player toneIndigo
```

## Components

### Watch Capture

- [Live Match UI](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Views/LiveMatchView.swift): `watchOS/PickledWatchApp/Views/LiveMatchView.swift`
- [Live Match State](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift): `watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift`
- [Motion Capture](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Motion/MotionCapturePipeline.swift): `watchOS/PickledWatchApp/Motion/MotionCapturePipeline.swift`
- [Workout Session](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/WorkoutSessionController.swift): `watchOS/PickledWatchApp/Services/WorkoutSessionController.swift`
- [Watch Sync Sender](https://github.com/lzrant/picklediq/blob/main/watchOS/PickledWatchApp/Services/WatchConnectivitySender.swift): `watchOS/PickledWatchApp/Services/WatchConnectivitySender.swift`

### iPhone App

- [Phone Sync Receiver](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Services/PhoneConnectivityController.swift): `iOS/PickledApp/Services/PhoneConnectivityController.swift`
- [App State](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/ViewModels/AppViewModel.swift): `iOS/PickledApp/ViewModels/AppViewModel.swift`
- [Analytics Dashboard](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/DashboardView.swift): `iOS/PickledApp/Views/DashboardView.swift`
- [Match History](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/MatchHistoryView.swift): `iOS/PickledApp/Views/MatchHistoryView.swift`
- [Paddle Bag](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/PaddleBagView.swift): `iOS/PickledApp/Views/PaddleBagView.swift`
- [Social Feed](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/SocialView.swift): `iOS/PickledApp/Views/SocialView.swift`
- [Profile](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/ProfileView.swift): `iOS/PickledApp/Views/ProfileView.swift`
- [App Navigation](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Views/RootTabView.swift): `iOS/PickledApp/Views/RootTabView.swift`
- [Match Sync Client](https://github.com/lzrant/picklediq/blob/main/iOS/PickledApp/Services/FirebaseSyncClient.swift): `iOS/PickledApp/Services/FirebaseSyncClient.swift`

### Shared Domain

- [Shot Classifier](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/ShotClassifier.swift): `Shared/Sources/PickledShared/Services/ShotClassifier.swift`
- [Sync Messages](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/SessionSyncMessage.swift): `Shared/Sources/PickledShared/Services/SessionSyncMessage.swift`
- [Paddle Analytics](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/PaddleAnalytics.swift): `Shared/Sources/PickledShared/Services/PaddleAnalytics.swift`
- [Paddle Library](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/PaddleLibrary.swift): `Shared/Sources/PickledShared/Services/PaddleLibrary.swift`
- [DUPR Abstraction](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Services/DUPRService.swift): `Shared/Sources/PickledShared/Services/DUPRService.swift`
- [Match Model](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/Match.swift): `Shared/Sources/PickledShared/Models/Match.swift`
- [Paddle Models](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/Paddle.swift): `Shared/Sources/PickledShared/Models/Paddle.swift`
- [Shot Events](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/ShotEvent.swift): `Shared/Sources/PickledShared/Models/ShotEvent.swift`
- [User Profile](https://github.com/lzrant/picklediq/blob/main/Shared/Sources/PickledShared/Models/UserProfile.swift): `Shared/Sources/PickledShared/Models/UserProfile.swift`

### Cloud Services

- [Cloud Functions](https://github.com/lzrant/picklediq/blob/main/Firebase/functions/index.js): `Firebase/functions/index.js`
- [Firestore](https://github.com/lzrant/picklediq/blob/main/Firebase/firestore.rules): `Firebase/firestore.rules`
- **DUPR Service**

### Other components

- **Player**

## Connections

- Player → Live Match UI: controls (evidence: `README.md`)
- Live Match UI → Live Match State: invokes (evidence: `watchOS/PickledWatchApp/Views/LiveMatchView.swift`)
- Live Match State → Motion Capture: starts capture (evidence: `watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift`)
- Motion Capture → Shot Classifier: classifies windows (evidence: `watchOS/PickledWatchApp/Motion/MotionCapturePipeline.swift`)
- Live Match State → Workout Session: controls workout (evidence: `watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift`)
- Live Match State → Watch Sync Sender: sends events (evidence: `watchOS/PickledWatchApp/Services/LiveMatchViewModel.swift`)
- Watch Sync Sender → Sync Messages: encodes messages (evidence: `watchOS/PickledWatchApp/Services/WatchConnectivitySender.swift`)
- Phone Sync Receiver → Sync Messages: decodes messages (evidence: `iOS/PickledApp/Services/PhoneConnectivityController.swift`)
- App State → Phone Sync Receiver: observes updates (evidence: `iOS/PickledApp/ViewModels/AppViewModel.swift`)
- App State → Match Sync Client: syncs matches (evidence: `iOS/PickledApp/ViewModels/AppViewModel.swift`)
- Match Sync Client → Cloud Functions: calls upsertMatch (evidence: `iOS/PickledApp/Services/FirebaseSyncClient.swift`)
- Cloud Functions → Firestore: uses data store
- App State → Match Model: manages matches (evidence: `iOS/PickledApp/ViewModels/AppViewModel.swift`)
- App State → Paddle Models: manages paddles (evidence: `iOS/PickledApp/ViewModels/AppViewModel.swift`)
- App State → User Profile: maintains profile (evidence: `iOS/PickledApp/ViewModels/AppViewModel.swift`)
- App State → Paddle Analytics: computes metrics
- App State → Paddle Library: loads paddles (evidence: `iOS/PickledApp/ViewModels/AppViewModel.swift`)
- App Navigation → Analytics Dashboard: routes to (evidence: `iOS/PickledApp/Views/RootTabView.swift`)
- App Navigation → Match History: routes to (evidence: `iOS/PickledApp/Views/RootTabView.swift`)
- App Navigation → Paddle Bag: routes to (evidence: `iOS/PickledApp/Views/RootTabView.swift`)
- App Navigation → Social Feed: routes to (evidence: `iOS/PickledApp/Views/RootTabView.swift`)
- App Navigation → Profile: routes to (evidence: `iOS/PickledApp/Views/RootTabView.swift`)
- Analytics Dashboard → App State: reads metrics (evidence: `iOS/PickledApp/Views/DashboardView.swift`)
- Match History → App State: reads history (evidence: `iOS/PickledApp/Views/MatchHistoryView.swift`)
- Paddle Bag → App State: manages bag (evidence: `iOS/PickledApp/Views/PaddleBagView.swift`)
- Social Feed → App State: reads social data (evidence: `iOS/PickledApp/Views/SocialView.swift`)
- Profile → App State: reads profile (evidence: `iOS/PickledApp/Views/ProfileView.swift`)
- DUPR Abstraction → DUPR Service: provides integration
