1# Teal Development Guidelines
2
3## Build Commands
4- Dev server: `turbo dev --filter=@teal/aqua`
5- Build all: `pnpm build`
6- Build Rust: `pnpm build:rust`
7- Test: `pnpm test`
8- DB migrate: `pnpm db:migrate`
9- DB reset: `pnpm db:reset`
10- Lexicon gen: `pnpm lex:gen`
11- Setup: `./scripts/setup-sqlx.sh`
12
13## Project Structure
14```
15teal/
16├── apps/aqua/ # Main Rust/Axum web app
17├── services/
18│ ├── cadet/ # AT Protocol jetstream consumer
19│ ├── rocketman/ # Jetstream library
20│ ├── satellite/ # Processing service
21│ ├── types/ # Shared types
22│ └── migrations/ # SQLx migrations
23├── lexicons/fm.teal.alpha/ # AT Protocol schemas
24│ ├── feed/ # Music play types
25│ ├── actor/ # Profile types
26│ └── stats/ # Analytics
27├── tools/lexicon-cli/ # Code generation
28└── compose.yaml # Docker setup
29```
30
31## Tech Stack
32- **Backend**: Rust (Axum, SQLx, Tokio, Serde, Tracing)
33- **Database**: PostgreSQL + Garnet/Redis
34- **Frontend**: TypeScript, Turbo, pnpm, Biome
35- **Protocol**: AT Protocol, IPLD/CAR, WebSocket streams
36- **Deploy**: Docker Compose, Traefik
37
38## Services
39- **Aqua**: HTTP API, OAuth, play submission, profiles, CAR import
40- **Cadet**: Real-time jetstream consumer, background jobs, metrics
41- **Rocketman**: Reusable WebSocket consumer framework
42- **Satellite**: Analytics, aggregation, external APIs
43
44## Database Schema
45```sql
46artists (mbid UUID, name TEXT, play_count INTEGER)
47releases (mbid UUID, name TEXT, play_count INTEGER)
48recordings (mbid UUID, name TEXT, play_count INTEGER)
49plays (uri TEXT, did TEXT, rkey TEXT, cid TEXT, track_name TEXT,
50 played_time TIMESTAMPTZ, recording_mbid UUID, release_mbid UUID,
51 submission_client_agent TEXT, music_service_base_domain TEXT)
52profiles (did TEXT, display_name TEXT, description TEXT, avatar BLOB, banner BLOB)
53```
54
55## AT Protocol Integration
56### Music Play Schema (`fm.teal.alpha.feed.play`)
57```json
58{
59 "trackName": "string (required)",
60 "trackMbId": "string", "recordingMbId": "string",
61 "duration": "integer", "artists": [{"name": "string", "mbid": "string"}],
62 "releaseName": "string", "releaseMbId": "string",
63 "isrc": "string", "originUrl": "string",
64 "musicServiceBaseDomain": "string", "submissionClientAgent": "string",
65 "playedTime": "datetime"
66}
67```
68
69### Profile Schema (`fm.teal.alpha.actor.profile`)
70```json
71{
72 "displayName": "string", "description": "string",
73 "featuredItem": {"mbid": "string", "type": "album|track|artist"},
74 "avatar": "blob", "banner": "blob", "createdAt": "datetime"
75}
76```
77
78## Core Features
79- **Music Tracking**: primarily MusicBrainz metadata, multi-platform support, scrobbling logic
80- **Social**: Federated profiles, activity feeds, featured items, rich text
81- **Real-time**: WebSocket streams, CAR processing, background jobs
82- **Analytics**: Play counts, charts, materialized views
83
84## Development Setup
85```bash
86# Install & setup
87pnpm install
88cp apps/aqua/.env.example apps/aqua/.env
89./scripts/setup-sqlx.sh
90
91# Start dependencies
92docker compose -f compose.dev.yml up -d garnet postgres
93
94# Run dev server
95turbo dev --filter=@teal/aqua
96# Access: http://localhost:3000
97```
98
99## Database Operations
100```bash
101pnpm db:create # Create DB
102pnpm db:migrate # Run migrations
103pnpm db:migrate:revert # Revert migration
104pnpm db:reset # Reset DB
105pnpm db:prepare # Prepare queries
106```
107
108## Lexicon Management
109```bash
110pnpm lex:gen # Generate types
111pnpm lex:watch # Watch & regenerate
112pnpm lex:validate # Validate schemas
113pnpm lex:diff # Show changes
114```
115
116## Rust Development Guidelines
117- **Error Handling**: Always use `anyhow::Result<T>` for fallible functions, never `Result<T, String>`
118- **Async Patterns**: Use `async/await` throughout, avoid `tokio::spawn` unless necessary for parallelism
119- **Database**: SQLx queries MUST be compile-time verified with `cargo sqlx prepare`
120- **Types**: Use workspace types from `types` crate, avoid duplicating structs across services
121- **Imports**: Group std → external → workspace → local, use `use` not `extern crate`
122- **Lifetimes**: Avoid explicit lifetimes unless required, let compiler infer
123- **Memory**: Use `Arc<T>` for shared ownership, `Rc<T>` only in single-threaded contexts
124- **Serialization**: Use `#[derive(Serialize, Deserialize)]` with serde, not manual impls
125- **Logging**: Use `tracing::info!`, `tracing::error!` etc, not `println!` or `log` crate
126- **Testing**: Place unit tests in `#[cfg(test)]` modules, integration tests in `tests/` directory
127
128## Git Workflow Guidelines
129- **CLAUDE: You MUST work in a branch, you can NOT push to main and thus you need to make a PR per-feature**
130- **Branch naming**: `feature/short-description`, `fix/issue-description`, `refactor/component-name`
131- **Commit format**: `type(scope): description` - e.g. `feat(cadet): add jetstream reconnection`, `fix(aqua): handle empty play submissions`
132- **Commit types**: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`, `style`
133- **Branch lifecycle**: Create from `main`, keep updated with `git rebase main`, squash before merge
134- **PR requirements**: All tests pass, no clippy warnings, SQLx queries prepared, lexicons validated
135- **Commit size**: Small, focused commits - one logical change per commit
136- **Message body**: Include WHY for non-obvious changes, reference issues with `#123`
137- **Breaking changes**: Mark with `BREAKING:` in commit body, update migration docs
138
139## Code Standards
140- **TypeScript**: Biome format (`pnpm fix`), explicit types
141- **DB**: Snake_case, migrations, indexes, constraints
142- **AT Protocol**: URI format, lexicon validation, federation-ready
143
144## Testing
145```bash
146pnpm test # All tests
147pnpm test:rust # Rust only
148cargo test # Service tests
149cargo tarpaulin # Coverage
150```
151
152## Deployment
153```bash
154# Environment vars: DATABASE_URL, REDIS_URL, AT_PROTOCOL_JWT_SECRET
155docker compose build
156docker compose up -d
157docker compose exec aqua-api pnpm db:migrate
158```
159
160## Architecture
161- **Microservices**: Rust services with shared types
162- **Federation**: AT Protocol for decentralized social music
163- **Scrobble When**: <2min = full play, ≥2min = half duration (max 4min)
164- **Data Flow**: WebSocket → Cadet → PostgreSQL → Aqua API
165- **Caching**: Redis for sessions, jobs, API responses
166- **Monitoring**: Prometheus metrics, structured logging
167
168## Key Patterns
169- **Lexicon-first**: Schema definitions drive code generation
170- **Event-driven**: Real-time processing via jetstream
171- **Content-addressable**: CAR files for federated data
172- **Type-safe**: SQLx compile-time query verification
173- **Async-first**: Tokio runtime throughout
174
175## Rust Anti-Patterns to Avoid
176- **DON'T**: Use `unwrap()` or `expect()` in production code - use proper error handling
177- **DON'T**: Clone unnecessarily - prefer borrowing with `&` or using `Arc<T>`
178- **DON'T**: Use `String` when `&str` suffices - avoid unnecessary allocations
179- **DON'T**: Mix blocking and async code - use `tokio::task::spawn_blocking` for CPU work
180- **DON'T**: Ignore compiler warnings - *fix all clippy lints before committing*
181- **DON'T**: Write raw SQL strings - use SQLx query macros or builder patterns
182- **DON'T**: Use global state - pass dependencies through function parameters or structs
183- **DON'T**: Implement `Debug` manually - use `#[derive(Debug)]` unless custom formatting needed