# Project Template: Native Betting Study App ## 1. Purpose Build a native mobile prototype for iOS and Android for a research study. Users watch a short sports clip before a critical moment, choose an outcome before a lock time, and then watch the reveal segment. The app must feel premium, fast, clear, mobile-first, and highly polished. It must feel as easy and fun to use as possible as it's needed for the research study. As mentioned, it is for research and prototype use, not first-release real-money gambling. However, the app will be tests on a small group and compared against traditional betting sites. We thereful need to make this as premium and good as possible to make sure that we can make meaningful observations and discussionpoints from the outcome. The app must support: - English - Swedish Every user-facing string must be localizable from day one. --- ## 2. Product Goals ### Primary goals - Native feel on both iOS and Android - Very high responsiveness - Smooth video playback - Strong gesture support - Clear and fair lock timing - Excellent readability and touch ergonomics - Premium visual quality - Stable architecture for long-term iteration ### Secondary goals - Low initial app size - Reusable backend - Easy experimentation between: - `control` - `modern` ### Non-goals - Real money betting in the first version - Full regulatory launch in version one --- ## 3. Technology Stack ## Backend - Rust - Axum - Tokio - SQLx - PostgreSQL - Valkey (redis) - tracing - thiserror + anyhow - validator - OpenTelemetry - Docker - docker compose ## iOS - Swift - SwiftUI - UIKit where needed - AVFoundation / AVPlayer - URLSession - async/await - native haptics - native gesture handling ## Android - Kotlin - Jetpack Compose - Media3 / ExoPlayer - Kotlin coroutines - Kotlin Flow - Kotlinx Serialization - kotlinx-datetime - kotlinx-collections-immutable - native haptics - native gesture handling ## Shared principles - No cross-platform UI - Native UI on each platform - Shared contracts through generated schemas and strict API specs - Prefer Kotlinx libraries broadly inside Android and Kotlin tooling - If a small shared Kotlin module is added later, prefer: - coroutines - Flow - Kotlinx Serialization - kotlinx-datetime - kotlinx-collections-immutable --- ## 4. Architecture Overview The system has three layers: ### 4.1 Client layer Two separate native apps: - `ios-app` - `android-app` Client responsibilities: - UI rendering - video playback - gestures - odds overlay - local state - media prefetch - clock sync - sending user selections - analytics and telemetry capture - localization in English and Swedish ### 4.2 API layer Rust backend responsible for: - auth - sessions - event feed - odds distribution - bet acceptance - settlement - experiment assignment - analytics ingestion - audit trail ### 4.3 Data layer - PostgreSQL for durable relational data - Redis for short-lived state and caching - object storage / CDN for HLS assets and media metadata --- ## 5. Repository Structure ```text root/ README.md docs/ product/ architecture/ api/ ux/ research/ localization/ analytics/ backend/ Cargo.toml Cargo.lock migrations/ src/ main.rs config.rs app_state.rs error.rs telemetry.rs db/ auth/ users/ sessions/ events/ media/ markets/ outcomes/ odds/ bets/ settlement/ analytics/ experiments/ localization/ admin/ jobs/ utils/ tests/ mobile/ ios-app/ android-app/ contracts/ openapi/ protobuf/ localization/ fixtures/ videos/ manifests/ test-data/ scripts/ infra/ ``` --- ## 6. Core Principles 1. The server is authoritative - lock time is determined by the server - odds acceptance is determined by the server - settlement is determined by the server 2. The client must not decide fairness - the client sends intent - the server accepts or rejects 3. Video must never block on UI updates - player and overlay must be architecturally separate 4. Explicit state machines are required - client and server must follow the same state model 5. Optimize for smoothness - low input latency - consistent animation timing - clear hierarchy - readable odds and timers 6. Localization is first-class - all UI strings in English and Swedish - no hardcoded copy in views - use stable translation keys 7. Log aggressively - log as many meaningful user actions as possible - support later UX optimization and research analysis - keep logs structured and queryable --- ## 7. Main Product Flow A user session contains multiple rounds. Each round: 1. Prefetch the next event 2. Show the preview segment 3. Update odds during preview 4. User selects an outcome before `lock_at` 5. Show locked state 6. Play reveal segment 7. Show result 8. Prepare the next round --- ## 8. Domain Model ## Core entities ### User - id - external_ref - created_at - preferred_language - device_platform ### Session - id - user_id - started_at - ended_at - experiment_variant - app_version - device_model - os_version - locale_code ### Event - id - sport_type - source_ref - title_en - title_sv - status - preview_start_ms - preview_end_ms - reveal_start_ms - reveal_end_ms - lock_at - settle_at ### EventMedia - id - event_id - media_type - hls_master_url - poster_url - duration_ms - preview_start_ms - preview_end_ms - reveal_start_ms - reveal_end_ms ### Market - id - event_id - question_key - market_type - status - lock_at - settlement_rule_key ### Outcome - id - market_id - outcome_code - label_key - sort_order ### OddsVersion - id - market_id - version_no - created_at - is_current ### OutcomeOdds - id - odds_version_id - outcome_id - decimal_odds - fractional_num - fractional_den ### BetIntent - id - user_id - session_id - event_id - market_id - outcome_id - idempotency_key - client_sent_at - server_received_at - accepted - acceptance_code - accepted_odds_version_id ### Settlement - id - market_id - settled_at - winning_outcome_id ### ExperimentAssignment - id - user_id - session_id - variant - assigned_at ### LocalizationKey - id - key_name - description ### LocalizationValue - id - localization_key_id - locale_code - text_value ### AnalyticsEventType - id - event_name - description ### AnalyticsEvent - id - session_id - user_id - analytics_event_type_id - occurred_at - app_screen_id - event_sequence_no ### AnalyticsEventAttribute - id - analytics_event_id - attribute_name - attribute_value ### AuditLog - id - actor_type - actor_id - action_name - created_at - target_type - target_id ### AuditLogAttribute - id - audit_log_id - attribute_name - attribute_value --- ## 9. State Machines ### Event lifecycle - `scheduled` - `prefetch_ready` - `preview_open` - `locking` - `locked` - `reveal_open` - `settled` - `archived` ### Client round state - `idle` - `prefetching` - `ready` - `preview_playing` - `selection_pending` - `selection_submitting` - `selection_accepted` - `locked` - `reveal_playing` - `result_visible` - `transitioning` - `error` ### Bet acceptance state - `received` - `validated` - `accepted` - `rejected_too_late` - `rejected_invalid_market` - `rejected_invalid_session` - `rejected_duplicate` - `settled` --- ## 10. Backend Modules ## 10.1 auth Responsibilities: - anonymous study sign-in or simple participant sign-in - token issuance - session binding ## 10.2 users Responsibilities: - user creation - locale preference - participant metadata ## 10.3 sessions Responsibilities: - start and end sessions - attach device metadata - track experiment group ## 10.4 events Responsibilities: - list events - return next round - return event manifest ## 10.5 media Responsibilities: - expose media metadata - expose HLS URLs - expose cue points ## 10.6 markets Responsibilities: - create and query markets - attach outcomes - manage lock time ## 10.7 odds Responsibilities: - publish odds versions - stream odds changes - store full relational odds history ## 10.8 bets Responsibilities: - receive bet intents - validate requests - accept or reject against lock time - enforce idempotency ## 10.9 settlement Responsibilities: - store winning outcome - expose settlement result - audit all settlement actions ## 10.10 experiments Responsibilities: - assign `control` or `modern` - return feature flags - keep assignment stable per session ## 10.11 analytics Responsibilities: - ingest all telemetry - normalize event types - write event attributes to child tables - support analysis exports ## 10.12 localization Responsibilities: - serve language bundles - support English and Swedish - version translation payloads ## 10.13 admin Responsibilities: - create events - create markets - create odds versions - publish fixtures - set settlements --- ## 11. Database Design Use SQLx migrations only. Do not use JSONB. Use normalized relational tables instead. ### Required tables - users - sessions - events - event_media - markets - outcomes - odds_versions - outcome_odds - bet_intents - settlements - experiment_assignments - localization_keys - localization_values - analytics_event_types - analytics_events - analytics_event_attributes - audit_logs - audit_log_attributes ### General rules - UUID primary keys - `timestamptz` for all timestamps - no JSONB columns - use child tables for attributes and event payloads - store translation text in relational tables - use foreign keys everywhere possible ### Key indexes - `sessions.user_id` - `sessions.started_at` - `events.status` - `markets.event_id` - `markets.lock_at` - `bet_intents.session_id` - `bet_intents.market_id` - `bet_intents.idempotency_key` - `analytics_events.session_id` - `analytics_events.user_id` - `analytics_events.analytics_event_type_id` - `audit_logs.created_at` ### Notes For analytics and audit data, avoid schemaless storage. Use parent-child tables such as: - `analytics_events` - `analytics_event_attributes` - `audit_logs` - `audit_log_attributes` This allows queryable, indexable telemetry without JSONB. --- ## 12. API Design ## 12.1 Style - REST for initialization and main flows - WebSocket or SSE for live odds and state updates - JSON transport is acceptable over the API - database storage must remain relational - version prefix: `/api/v1` ## 12.2 Core endpoints ### Session - `POST /api/v1/session/start` - `POST /api/v1/session/end` - `GET /api/v1/session/me` ### Feed and events - `GET /api/v1/feed/next` - `GET /api/v1/events/{event_id}` - `GET /api/v1/events/{event_id}/manifest` ### Markets and odds - `GET /api/v1/events/{event_id}/markets` - `GET /api/v1/markets/{market_id}/odds/current` - `GET /api/v1/stream` ### Bets - `POST /api/v1/bets/intent` - `GET /api/v1/bets/{bet_intent_id}` ### Results - `GET /api/v1/events/{event_id}/result` ### Analytics - `POST /api/v1/analytics/batch` ### Experiments - `GET /api/v1/experiments/config` ### Localization - `GET /api/v1/localization/{locale_code}` ### Admin - `POST /api/v1/admin/events` - `POST /api/v1/admin/markets` - `POST /api/v1/admin/odds` - `POST /api/v1/admin/settlements` --- ## 13. Native Client Architecture ## 13.1 Common structure Each app must have: - Presentation - Feature state - Domain - Networking - Media - Localization - Analytics - Persistence ## 13.2 iOS structure ```text ios-app/ App/ Core/ Networking/ DesignSystem/ Analytics/ Media/ Haptics/ Gestures/ Localization/ Features/ Onboarding/ Feed/ Round/ Selection/ Reveal/ Result/ Session/ Settings/ Resources/ Tests/ ``` ### iOS rules - SwiftUI by default - UIKit only where precision control is needed - AVPlayer for playback - separate player state from overlay state - all user strings loaded from localization layer - English and Swedish supported at launch ## 13.3 Android structure ```text android-app/ app/ core/ network/ designsystem/ analytics/ media/ haptics/ gestures/ localization/ data/ domain/ feature/ onboarding/ feed/ round/ selection/ reveal/ result/ session/ settings/ benchmark/ androidTest/ ``` ### Android rules - Jetpack Compose by default - Media3 / ExoPlayer for playback - ViewModel + immutable state - Kotlin coroutines and Flow everywhere practical - Kotlinx Serialization for API models - kotlinx-datetime for time handling - kotlinx-collections-immutable for UI collections - optimize recomposition carefully - English and Swedish supported at launch --- ## 14. Gesture Specification The app must feel mobile-native and gesture-friendly. ### Supported gestures - tap to select outcome - vertical swipe between rounds or cards where appropriate - swipe down to dismiss modal - edge gestures only where platform expectations exist - no horizontal scrubbing during an active betting round ### Rules - gestures must never make selection state ambiguous - a critical choice must get clear visual confirmation - avoid complex multi-touch gestures - no gesture may fight system navigation - touch targets must be large and comfortable ### Haptics - subtle feedback when a selection is accepted - subtle feedback when the market locks - never overuse haptics --- ## 15. Screens ### Onboarding - study intro - consent - language selection - start session ### Feed - next round preview card - compact event information - clear call to action ### Active round - video - countdown - odds panel - selection controls - lock status - pause or exit control ### Locked state - selected outcome highlighted - lock status clearly visible - odds frozen for the user ### Reveal - reveal segment - simple visual emphasis - no chaotic effects ### Result - what happened - user selection - outcome display - next round entry ### Pause and exit - easy to reach - easy to confirm - no manipulative friction --- ## 16. Design System ### Visual direction - premium dark base - strong contrast - limited accent colors - clean typography - large video surface - restrained motion - high readability ### Design tokens Define centrally: - colors - spacing - radii - elevation - typography scale - icon sizes - motion durations - motion curves ### Motion rules - quick feedback on touch - calm transitions - one consistent motion language - no flashing or overly aggressive effects - reveal should feel polished, not loud --- ## 17. Localization Requirements The app must ship with: - English - Swedish ### Rules - every visible string uses a localization key - no hardcoded strings in views - backend can serve localization bundles if needed - use pluralization support where relevant - event and market text must support both languages - analytics must include active locale for each session ### Suggested locale codes - `en` - `sv` --- ## 18. Video and Media Pipeline ### Requirements - HLS as the default streaming format - short segments - correct keyframes around: - preview start - preview end - lock cue - reveal start - reveal end ### Playback goals - prefetch the next clip early - keep player and overlay independent - recover gracefully from playback errors - support poster fallback ### Cue points Each media item must define: - preview start - preview end - lock cue - reveal start - reveal end --- ## 19. Fairness and Clock Sync ### Rule The client may display a local countdown based on synced server time, but the server always decides final bet acceptance. ### Requirements - regular server clock sync - client clock drift compensation - request tracing for all bet intents - strict idempotency keys - duplicate submissions must be safe ### Logged timestamps For all critical actions, capture: - client timestamp - server receive timestamp - request id - session id - app version - locale - device platform --- ## 20. Analytics and Telemetry Log as many meaningful actions as possible to support later optimization, analysis, and UX refinement. ## 20.1 Core rule All analytics events must be structured. Do not store opaque analytics blobs in JSONB. Use event tables and attribute tables. ## 20.2 Minimum tracked actions - app_opened - app_backgrounded - app_closed - language_selected - consent_viewed - consent_accepted - session_started - session_ended - feed_viewed - round_card_viewed - round_loaded - event_manifest_received - preview_prefetch_started - preview_prefetch_completed - preview_started - preview_paused - preview_resumed - preview_completed - countdown_visible - countdown_warning_threshold_hit - odds_panel_viewed - odds_version_received - odds_changed - outcome_focused - outcome_selected - selection_submitted - selection_accepted - selection_rejected - duplicate_selection_attempt - market_locked - reveal_started - reveal_completed - result_viewed - next_round_requested - session_paused - exit_prompt_viewed - exit_confirmed - playback_error - network_error - stream_reconnected - localization_bundle_loaded - locale_changed - haptic_triggered - gesture_swipe - gesture_tap - gesture_cancelled - screen_viewed - cta_pressed ## 20.3 Analytics event schema Store: - event row in `analytics_events` - one row per attribute in `analytics_event_attributes` Example attributes: - screen_name - event_id - market_id - outcome_id - odds_version_id - countdown_ms_remaining - locale_code - experiment_variant - network_type - device_orientation - playback_position_ms - latency_ms ## 20.4 Research metrics - decision latency - round completion rate - missed rounds - post-lock selection attempts - replay desire - exit rate - difference between control and modern - effect of locale - effect of device platform --- ## 21. Audit Logging Audit logging is separate from analytics. ### Audit goals - fairness - traceability - admin accountability - settlement trace ### Audit examples - event created - market opened - odds version published - market locked - bet accepted - bet rejected - result settled - admin override applied - localization updated Store: - `audit_logs` - `audit_log_attributes` --- ## 22. Security - signed tokens - strict input validation - rate limiting - request ids - full tracing - secrets outside the repo - environment-based config - server-side experiment assignment - server-side odds authority --- ## 23. Testing Strategy ## Backend - unit tests per module - integration tests with PostgreSQL - SQLx compile-time query checks - state transition tests - API contract tests - migration tests ## iOS - unit tests for view models and reducers - snapshot tests for core screens - UI tests for the full round flow - playback error tests - localization tests for English and Swedish ## Android - unit tests for reducers and view models - Compose UI tests - macrobenchmark tests - playback tests - localization tests for English and Swedish - coroutine and Flow tests ## End-to-end - start session - assign variant - load event - prefetch preview - start preview - submit selection - lock market - play reveal - settle result - ingest analytics --- ## 24. Performance Goals ## Backend - p95 standard GET < 100 ms in staging-like conditions - p95 `POST /bets/intent` < 150 ms - low odds stream latency ## Mobile - no visible jank in the round flow - very fast touch feedback - next round prefetched before the current one ends - low memory churn on screen transitions --- ## 25. CI and CD ## Backend pipeline - rustfmt - clippy - tests - SQLx checks - migration validation - build container ## iOS pipeline - lint - unit tests - UI tests - localization checks ## Android pipeline - ktlint or equivalent - detekt if adopted - unit tests - Compose tests - benchmark checks where practical - localization checks ## General - PR template - changelog - release tags - API contract versioning --- ## 26. Configuration Profiles Minimum environments: - `local` - `dev` - `staging` - `study` - `prod` Each environment must define: - backend URL - stream URL - CDN base URL - analytics mode - experiment config - localization source - log verbosity --- ## 27. Feature Flags Must support flags for: - control vs modern - animation intensity - haptics enabled - autoplay next enabled or disabled - countdown style - odds update visualization - result card style - localization source mode --- ## 28. AI Agent Execution Plan The AI agent must work in this order. ### Phase 1: Documents 1. Write root README 2. Write architecture document 3. Write API spec 4. Write relational schema 5. Write SQLx migrations 6. Write state machine spec 7. Write localization key catalog 8. Write analytics taxonomy ### Phase 2: Backend foundation 1. Initialize Rust workspace 2. Add Axum, Tokio, SQLx, tracing, serde, config 3. Add config system 4. Add app state 5. Add error handling 6. Add health endpoint 7. Connect PostgreSQL 8. Add migrations 9. Add Redis 10. Add session endpoints ### Phase 3: Domain and API 1. Implement users 2. Implement sessions 3. Implement events 4. Implement event media 5. Implement markets 6. Implement outcomes 7. Implement odds versions 8. Implement bet intents 9. Implement settlement 10. Implement experiments 11. Implement localization 12. Implement analytics ### Phase 4: Fixtures and admin 1. Add seed scripts 2. Add fixture loading 3. Add admin endpoints 4. Add test event generator ### Phase 5: iOS app 1. Initialize project structure 2. Build design system 3. Build localization layer 4. Build networking client 5. Build feed screen 6. Build round screen 7. Integrate AVPlayer 8. Build countdown and odds overlay 9. Build selection flow 10. Build reveal and result 11. Add analytics ### Phase 6: Android app 1. Initialize project structure 2. Build design system 3. Build localization layer 4. Build networking client 5. Use Kotlin coroutines and Flow broadly 6. Use Kotlinx Serialization for API models 7. Use kotlinx-datetime for time handling 8. Build feed screen 9. Build round screen 10. Integrate Media3 11. Build countdown and odds overlay 12. Build selection flow 13. Build reveal and result 14. Add analytics ### Phase 7: Quality 1. Add tests 2. Validate localization 3. benchmark critical paths 4. review gesture edge cases 5. validate analytics coverage 6. document research export path --- ## 29. Definition of Done A version is done when: - the user can start a session - a variant is assigned - the next event is loaded - preview video plays - odds are shown and updated - the user can select an outcome - the server accepts or rejects correctly - lock state is clear - reveal plays - result is shown - analytics are written relationally - English and Swedish both work - the flow passes end-to-end tests --- ## 30. Future Extensions - small shared Kotlin module for non-UI state logic - researcher admin web panel - analytics export to CSV or Parquet - accessibility pass - tablet and foldable layouts - richer localization management - advanced telemetry dashboards --- ## 31. Important Constraints - no real money flow in the first version - localization in English and Swedish is mandatory - normalized relational logging is mandatory - avoid JSONB in database design - log broadly enough to enable later UX optimization