1450 lines
26 KiB
Markdown
1450 lines
26 KiB
Markdown
# Project Template: Native Betting Study App
|
|
|
|
This is the canonical working plan and progress log for the project. Use this file for progress notes, next steps, and status updates.
|
|
|
|
## Current Status
|
|
|
|
### Done So Far
|
|
|
|
- Android was renamed from the old study app into `com.hermes.app`, with the app entry, theme, repository, media, and feature screens rewritten around the backend-backed Hermes flow.
|
|
- Android no longer relies on the sample fixture; it boots from backend session and round data, uses `/health.server_time` for clock sync, buffers analytics, and flushes them to the backend.
|
|
- Android localization is in place for English and Swedish, including the remaining locale toggle labels and session language display.
|
|
- Backend now exposes `server_time` from `/health`, publishes the matching OpenAPI contract, and records audit events for session, bet, event, market, odds, and settlement actions.
|
|
- Backend smoke coverage was added for `server_time` and audit logging.
|
|
- iOS now follows the backend-backed flow, syncs clock from `/health`, flushes analytics, and uses localized language labels instead of hardcoded `EN` and `SV`.
|
|
- iOS source was updated for the backend-backed session and round flow, including the real preview cue points and localized session strings.
|
|
- iOS settings now uses localized copy instead of a scaffold placeholder, and the screen is wired into the root scroll flow.
|
|
- Android debug build passes with `./gradlew :app:assembleDebug`.
|
|
- Backend tests pass with `cargo test`.
|
|
- Backend localization bundles now have a contract test, and the localization catalog matches the shipped English and Swedish keys.
|
|
- The research export path is documented under `docs/research/export-path.md`.
|
|
- Backend bet coverage now includes a direct regression for late-lock rejection.
|
|
- Backend bet coverage now includes invalid-session and invalid-market rejection regressions, plus settlement override coverage.
|
|
- Backend analytics coverage now includes a representative key user flow batch.
|
|
- iOS project scaffolding now exists as `mobile/ios-app/HermesApp.xcodeproj` with a shared scheme and XCTest target.
|
|
- iOS sources and tests build and pass in Xcode on an iPhone simulator, and a standalone localization/error-mapping harness passes.
|
|
- Android local unit tests now cover localization, error mapping, and analytics batch conversion, and `./gradlew testDebugUnitTest` passes with the Android SDK installed.
|
|
- Android swipe-down gesture handling now uses cumulative drag distance, and edge-case tests cover upward drags, slow swipes, and per-gesture single-trigger behavior.
|
|
- iOS now defaults to a demo test mode with mock rounds, and the primary round flow is a fullscreen swipe-driven video experience with a 15-second lock timer and accelerated reveal transition.
|
|
- The fullscreen round interaction contract is now documented in `docs/mobile-fullscreen-round-flow.md`, including demo-mode rules, side-tint swipe feedback, and the requirement to finish the accelerated round clip before reveal starts.
|
|
|
|
### Still Open
|
|
|
|
- Continue through the remaining plan phases and finish any leftover localization and polish work.
|
|
- Keep the iOS project in sync with the plan as the app grows.
|
|
- Keep expanding tests around session, odds, settlement, and analytics behavior.
|
|
- Align Android with the clarified fullscreen swipe flow and demo-mode testing path.
|
|
|
|
## 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.
|
|
|
|
After each major completed change set, create a git commit and push it before starting the next major change.
|
|
|
|
### 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
|