Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5b026a0
chore(android): update build configuration for SDK 36 and Kotlin 2.1
romdj Jan 25, 2026
06ecf37
chore(ios/macos): add CocoaPods configuration and plugin registration
romdj Jan 25, 2026
a14d1ea
feat(mobile): add configurable PTT button data models
romdj Jan 25, 2026
34a6423
feat(android): implement multi-button PTT with voice assistant preven…
romdj Jan 25, 2026
65b6f95
feat(ios): implement multi-button PTT with volume button observer
romdj Jan 25, 2026
113731f
feat(mobile): add PTT service configuration management
romdj Jan 25, 2026
67249bc
feat(mobile): add PTT settings UI and on-screen button
romdj Jan 25, 2026
b235c84
docs: add PTT implementation documentation and testing guides
romdj Jan 25, 2026
7c01bc8
chore: update project configuration and add helper scripts
romdj Jan 25, 2026
364208d
feat(signaling): add WebRTC signaling service
romdj Jan 30, 2026
9ebb8bc
feat(mobile): add WebRTC and signaling client services
romdj Jan 30, 2026
4a1e8eb
feat(mobile): add call screen with WebRTC integration
romdj Jan 30, 2026
53397bb
feat(infra): add local dev and k3s deployment configs
romdj Jan 30, 2026
901e5b6
docs: update README with MVP architecture and quick start
romdj Jan 30, 2026
ef6ec05
feat(android): refactor PTT to foreground MediaSessionService
romdj Jun 3, 2026
6ef3f06
feat(mobile): add PTT recorder loopback and sync config to native
romdj Jun 3, 2026
921fb0c
feat(ios): integrate PushToTalk framework for system PTT
romdj Jun 3, 2026
41971ed
test(mobile): add PTTService unit tests
romdj Jun 3, 2026
8153f18
docs: correct stale PTT blocker framing across project docs
romdj Jul 23, 2026
ba70624
docs: add CI/CD pipeline plan (parallel builds, staged test gates)
romdj Jul 30, 2026
4b3e018
docs: add PTT->WebRTC 2-device walkie-talkie MVP design spec
romdj Aug 2, 2026
b384045
docs: add PTT->WebRTC MVP implementation plan (5 TDD tasks)
romdj Aug 14, 2026
0ae5895
feat(mobile): add club BBB / 7-group model with room-id mapping
romdj Aug 14, 2026
b8f5dce
refactor(mobile): extract VoiceTransport and SignalingChannel interfaces
romdj Aug 14, 2026
d637583
feat(mobile): add RideSession coordinating PTT, WebRTC mute, signaling
romdj Aug 16, 2026
55b8d02
feat(mobile): add club BBB group picker screen
romdj Aug 16, 2026
1ae0950
feat(mobile): drive CallScreen transmit through RideSession, route vi…
romdj Aug 23, 2026
067e709
chore(mobile): regenerate desktop plugin registrants (jni FFI)
romdj Aug 23, 2026
f746dba
chore(android): pin Flutter migrator DSL flags in gradle.properties
romdj Aug 24, 2026
32f06bc
feat(android): capture Bluetooth headset volume buttons for PTT
romdj Aug 24, 2026
7ec6321
chore(signaling): stop tracking build binary and apply gofmt
romdj Sep 13, 2026
c3ec0d2
test(signaling): add hub unit tests and WebSocket integration tests
romdj Sep 13, 2026
dff98d6
fix(signaling): parse ALLOWED_ORIGINS as a comma-separated list
romdj Sep 13, 2026
c3dca36
fix(signaling): patch reachable gorilla/websocket vulnerability
romdj Sep 13, 2026
b94032e
style(mobile): apply dart format and clear analyzer infos
romdj Sep 13, 2026
abd8b3e
ci: add unified staged pipeline with an aggregate required check
romdj Sep 13, 2026
10d1e0b
chore: add lefthook hooks mirroring the fast CI gates
romdj Sep 13, 2026
8369ff6
docs: document the CI pipeline and local gates
romdj Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 49 additions & 93 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,114 +1,70 @@
# Peloton Communicator Project

## Overview
This project is a **walkie-talkie style communication app** for people riding Peloton bikes together. The core concept is to enable **push-to-talk (PTT) functionality using Bluetooth headset play/pause buttons** as triggers for voice communication between riders.
A **walkie-talkie style communication app** for people riding (Peloton) bikes together. Push-to-talk (PTT) is triggered by Bluetooth headset buttons, phone hardware buttons, an on-screen button, or the iOS system PTT UI, and voice is streamed to other riders over WebRTC.

### Project Components
- A Flutter mobile app (`/app/`) for cross-platform client applications
- A Go backend server (`/server/`) using Gin framework with REST API
- Infrastructure configuration for AWS and GCP deployments (`/aws/`, `/gcp/`)
- WebRTC demo implementation (`/flutter-webrtc-demo/`)
## Status (July 2026)

### Core Innovation: Bluetooth Headset PTT
The **major technical challenge** and primary focus is capturing Bluetooth headset play/pause button events to trigger voice recording/transmission. This creates a hands-free communication experience essential for cycling.
**The historic "major blocker" — capturing Bluetooth headset button events — is solved.** Do not re-investigate it from scratch; see `docs/bluetooth-ptt-implementation.md` for the working architecture and `ROADMAP.md` for what remains.

## Architecture
- **Frontend**: Flutter app with Material Design
- **Backend**: Go server with Gin framework, RESTful API
- **API Documentation**: Swagger/OpenAPI specification in `documentation.yaml`
- **Infrastructure**: Terraform configurations for cloud deployment
- **Android**: A Media3 `MediaSessionService` foreground service (`PttMediaSessionService.kt`) owns the media session and intercepts BT play/pause/headsethook key events, even when the app is backgrounded or the screen is off. Events flow `PttEventBus` → `MainActivity` → MethodChannel → `ptt_service.dart`. Volume buttons are captured in the activity (`onKeyDown`/`onKeyUp`).
- **iOS**: Apple's **PushToTalk framework** (iOS 16+, `PTTSystemManager.swift`) provides system PTT UI, background transmit, and headset button events via `setAccessoryButtonEventsEnabled(true)`. This framework (released 2022) is the official platform unlock — use it, don't fight MPRemoteCommandCenter.
- **WebRTC**: Signaling service (Go, `packages/services/signaling`), Flutter signaling client, and WebRTC service are implemented. PTT→WebRTC integration and NAT traversal (STUN/TURN) are the current MVP focus.

## Key Components
### Known Bluetooth protocol constraints (not bugs — do not try to "fix" in-app)
1. **Hold-to-talk on BT play/pause is unreliable.** Most headset firmware buffers the button to disambiguate single/double/long presses, so the AVRCP command arrives as a press+release pair at physical release; Android may also suppress `ACTION_UP` for BT devices. The app therefore **forces toggle mode for the play/pause button** — keep that behavior.
2. **Headset volume buttons don't generate KeyEvents.** With AVRCP absolute volume, the headset sends `SET_ABSOLUTE_VOLUME` straight to the audio system. Implemented workaround: `PttPlayer` claims remote device volume (Media3's `VolumeProvider` equivalent) while the volume button is selected, and routes steps through `PttEventBus.emitDiscrete()` — toggle mode only, never hold.
3. True press-and-hold with reliable down/up is achievable with **dedicated BLE PTT buttons** (handlebar-mountable; the Zello/ESChat ecosystem) — a candidate premium path.

### Flutter App (`/app/`)
- Main entry point: `lib/main.dart`
- Current state: Basic counter app template (needs development)
- Target platforms: iOS, Android, Web, Desktop (Linux, macOS, Windows)

### Go Server (`/server/`)
- Main file: `main.go` (currently a basic albums API example)
- Framework: Gin v1.7.2
- API specification: Based on `documentation.yaml` swagger spec
- Generated code: `server/generated/` contains auto-generated API handlers
## Repository Structure (monorepo)
```
├── packages/
│ ├── mobile/ # Flutter app (iOS/Android)
│ │ ├── lib/services/ # ptt_service, recorder_service, WebRTC, signaling client
│ │ ├── android/…/app/ # MainActivity, PttMediaSessionService, PttEventBus, PttPlayer
│ │ └── ios/Runner/ # AppDelegate, PTTSystemManager (PushToTalk framework)
│ ├── server/ # Go backend API server (legacy)
│ ├── services/signaling/ # WebRTC signaling service (Go, WebSocket)
│ └── infra/ # docker-compose, k3s manifests
├── docs/ # bluetooth-ptt-implementation.md and others
├── ROADMAP.md # Current diagnosis + next steps
└── scripts/ # Build and deployment scripts
```

### API Specification
- Swagger 2.0 specification in `documentation.yaml`
- Endpoints for:
- Club management (CRUD operations)
- User management and authentication
- Store/order functionality
- File upload capabilities
## Communication Flow
1. Button press (headset / hardware / on-screen / iOS system PTT) → `pttPressed` over MethodChannel
2. `ptt_service.dart` drives state; recorder starts (POC) — target: unmute WebRTC audio track
3. Release/toggle → `pttReleased` → stop transmitting

## Development Guidelines

### Testing
- Flutter: Use `flutter test` for unit and widget tests
- Go: Use `go test` for backend testing
- Flutter: `flutter test` in `packages/mobile`
- Signaling (Go): `go test -race ./...` (unit) and `go test -race -tags=integration ./cmd/...` (real WebSocket flows) in `packages/services/signaling`
- `packages/server` is legacy (placeholder only) and is not in CI
- Follow TDD practices where applicable

### CI and local gates
- Single pipeline: `.github/workflows/ci.yml` (quality → build & unit → integration → security → `CI Status`). Details and local repro commands: `docs/ci-pipeline.md`
- `CI Status` is the required check on `main`; add new jobs to its `needs:` list to make them merge-blocking
- Toolchain pins live in the workflow `env:` (Flutter 3.47.1, Go 1.27.x, Java 17); keep the signaling Dockerfile's Go version in sync
- Local hooks via lefthook (`brew install lefthook && lefthook install`): format + analyze/vet on commit, tests on push. Never use `--no-verify`
- Analyzer runs with `--fatal-infos`: infos (deprecations, missing `const`) fail the build
- **Headset behavior requires physical devices** (emulators insufficient); expect per-headset AVRCP variance
- Testing guides: `TESTING.md`, `PHASE*_TESTING.md`, `WIRELESS_DEBUG_SETUP.md`

### Code Organization
- Keep Flutter UI components modular and reusable
- Keep Flutter UI components modular and reusable; PTT state management lives in `ptt_service.dart` (Provider)
- All press/release paths converge on one state transition so input sources are indistinguishable downstream
- Follow Go best practices for package organization
- Use existing patterns established in the codebase

### API Development
- Follow the OpenAPI specification in `documentation.yaml`
- Implement proper error handling and validation
- Use OAuth2 and API key authentication as specified

### Infrastructure
- Terraform configurations available for AWS and GCP
- Docker support through generated Dockerfile in server

## Technical Challenges & Solutions

### Bluetooth Headset Integration
**Problem**: Capturing Bluetooth headset play/pause button events for PTT functionality
**Current Status**: Major blocker - multiple approaches attempted

**Attempted Solutions**:
1. **audio_service Package**: MediaSession approach with custom handlers
2. **flutter_blue_plus**: BLE approach for modern Bluetooth devices
3. **Native Platform Channels**: Direct Android/iOS media button handling

**Key Learnings**:
- Requires physical device testing (emulators insufficient)
- Android: MediaSessionCompat + BroadcastReceiver for media button events
- iOS: MPRemoteCommandCenter for media button handling
- Build system compatibility: Java 21 + Gradle 8.7 + AGP 8.4+

### Current Architecture (POC)
```
Bluetooth Headset -> Platform Channel -> Flutter App -> Record Audio -> Playback
```

### Future Architecture (Full Implementation)
```
Bluetooth Headset -> PTT Trigger -> Record -> WebRTC/UDP -> Other Riders
```

## Communication Flow
1. **Press play button** → Start recording microphone
2. **Release button** → Stop recording, immediately playback locally (POC)
3. **Future**: Stream to other riders in real-time via WebRTC

## Project Structure
```
/app/ # Flutter app (currently template)
/lib/services/ # Audio controller, media button handler
/lib/ui/ # UI components
/server/ # Go backend (currently example API)
/.claude/tmp/ # Previous implementation attempts/conversations
```

## Development Environment
- **Java**: 21 (requires Gradle 8.7+, AGP 8.4+)
- **Flutter**: Latest stable
- **Target Platforms**: Android 12+, iOS 14+
- **Testing**: Requires physical devices with Bluetooth headsets
- **Java**: 21 (Gradle 8.7+, AGP 8.4+); Android SDK 36, Kotlin 2.1
- **Flutter**: >= 3.16.0 · **Go**: >= 1.21
- **Target Platforms**: Android 12+, iOS 16+ (PushToTalk framework floor)
- iOS PushToTalk requires the `com.apple.developer.push-to-talk` entitlement and a real device

## Common Commands
- Flutter: `flutter run`, `flutter build`, `flutter test`
- Go: `go run main.go`, `go build`, `go test`
- Infrastructure: `terraform plan`, `terraform apply`
- Clean build: `flutter clean && flutter pub get`
- Mobile: `cd packages/mobile && flutter run` / `flutter test` / `flutter clean && flutter pub get`
- Signaling: `cd packages/services/signaling && go run ./cmd/main.go` (ws://localhost:8080/ws, GET /health)
- Infra: `docker compose -f packages/infra/docker-compose.yaml up`, k3s manifests in `packages/infra/k3s/`
3 changes: 3 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"model": "opus"
}
Loading
Loading