# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Svrnty Console is a Flutter-based management console for the Svrnty AI platform. It communicates with a C# CQRS backend using a type-safe, OpenAPI-driven API contract system. **Tech Stack:** - Flutter 3.x / Dart 3.9.2+ - CQRS API pattern (Command Query Responsibility Segregation) - OpenAPI 3.0.1 contract-first architecture - Custom theme: Crimson Red (#C44D58), Slate Blue (#475C6C) - Fonts: Montserrat (UI), IBM Plex Mono (code/technical) --- ## Essential Commands ### Development ```bash # Install dependencies flutter pub get # Run the application flutter run # Run on specific platform flutter run -d macos flutter run -d chrome ``` ### Testing & Quality ```bash # Run tests flutter test # Run static analysis flutter analyze # Verify API type safety (custom script) ./scripts/verify_api_types.sh ``` ### API Contract Updates ```bash # After backend updates openapi.json: cp ../backend/docs/openapi.json ./api-schema.json ./scripts/update_api_client.sh # Or run code generation directly: flutter pub run build_runner build --delete-conflicting-outputs # Clean build (if generation fails): flutter clean flutter pub get flutter pub run build_runner build --delete-conflicting-outputs ``` ### Build ```bash # Build for production flutter build macos flutter build web flutter build ios ``` --- ## Architecture Principles ### 1. OpenAPI-Driven API Contract The backend and frontend share a single source of truth: `api-schema.json` (OpenAPI specification). **Flow:** 1. Backend exports `docs/openapi.json` from C# controllers + XML docs 2. Frontend copies to `api-schema.json` 3. Code generation creates Dart types from contract 4. Frontend creates endpoint extensions using generated types **Key Files:** - `api-schema.json` - OpenAPI contract (copy from backend) - `lib/api/client.dart` - CQRS client implementation - `lib/api/types.dart` - Core types (Result, Serializable, errors) - `lib/api/endpoints/` - Type-safe endpoint extensions - `lib/api/generated/` - Auto-generated code (git-ignored) ### 2. CQRS Pattern All backend endpoints follow CQRS with three operation types: **Queries (Read):** ```dart final result = await client.executeQuery( endpoint: 'users/123', query: GetUserQuery(userId: '123'), fromJson: UserDto.fromJson, ); ``` **Commands (Write):** ```dart final result = await client.executeCommand( endpoint: 'createUser', command: CreateUserCommand(name: 'John', email: 'john@example.com'), ); ``` **Paginated Queries (Lists):** ```dart final result = await client.executePaginatedQuery( endpoint: 'users', query: ListUsersQuery(), itemFromJson: UserDto.fromJson, page: 1, pageSize: 20, filters: [FilterCriteria(field: 'status', operator: FilterOperator.equals, value: 'active')], sorting: [SortCriteria(field: 'createdAt', direction: SortDirection.descending)], ); ``` **Important:** ALL CQRS endpoints use JSON body via POST, even empty queries send `{}`. ### 3. Functional Error Handling Never use try-catch for API calls. Use the `Result` type: ```dart // Pattern matching final result = await client.getUser('123'); result.when( success: (user) => print('Hello ${user.name}'), error: (error) => print('Error: ${error.message}'), ); // Or switch expression final message = switch (result) { ApiSuccess(value: final user) => 'Hello ${user.name}', ApiError(error: final err) => 'Error: ${err.message}', }; ``` --- ## Mandatory Coding Standards ### Strict Typing - NO EXCEPTIONS See `.claude-docs/strict-typing.md` for complete requirements. **Core Rules:** 1. Every variable must have explicit type annotation 2. Every function parameter must be typed 3. Every function return value must be typed 4. **NEVER** use `dynamic` (Dart's version of `any`) 5. **NEVER** use untyped `var` declarations 6. Use proper generics, interfaces, and type unions **Examples:** ❌ FORBIDDEN: ```dart dynamic value = getValue(); void handleData(var data) { ... } ``` ✅ REQUIRED: ```dart UserData value = getValue(); void handleData(RequestData data) { ... } ``` ### Serializable Interface All queries, commands, and DTOs must implement: ```dart abstract interface class Serializable { Map toJson(); } // Example: class GetUserQuery implements Serializable { final String userId; const GetUserQuery({required this.userId}); @override Map toJson() => {'userId': userId}; } ``` --- ## Response Protocol See `.claude-docs/response-protocol.md` for complete protocol. **All responses must end with structured choices:** **Binary Choice:** ``` (always read claude.md to keep context between interactions) (Y) Yes — [brief action summary] (N) No — [brief alternative/reason] (+) I don't understand — ask for clarification ``` **Multiple Choice:** ``` (always read claude.md to keep context between interactions) (A) Option A — [≤8 words description] (B) Option B — [≤8 words description] (C) Option C — [≤8 words description] (+) I don't understand — ask for clarification ``` When user selects `(+)`, explain the concept, then re-present options. --- ## Adding New API Endpoints ### Step 1: Backend adds endpoint Backend team documents in C# controller with XML comments and exports `docs/openapi.json`. ### Step 2: Update contract ```bash cp ../backend/docs/openapi.json ./api-schema.json ./scripts/update_api_client.sh ``` ### Step 3: Create endpoint extension Create file in `lib/api/endpoints/`: ```dart import '../client.dart'; import '../types.dart'; extension UserEndpoint on CqrsApiClient { Future> getUser(String userId) async { return executeQuery( endpoint: 'users/$userId', query: GetUserQuery(userId: userId), fromJson: UserDto.fromJson, ); } } // Define query (matches backend contract) class GetUserQuery implements Serializable { final String userId; const GetUserQuery({required this.userId}); @override Map toJson() => {'userId': userId}; } // Define DTO (from OpenAPI schema) class UserDto { final String id; final String name; final String email; const UserDto({required this.id, required this.name, required this.email}); factory UserDto.fromJson(Map json) => UserDto( id: json['id'] as String, name: json['name'] as String, email: json['email'] as String, ); } ``` ### Step 4: Export from api.dart Add to `lib/api/api.dart`: ```dart export 'endpoints/user_endpoint.dart'; ``` --- ## Configuration ### API Client Setup **Development (localhost):** ```dart final client = CqrsApiClient( config: ApiClientConfig.development, // http://localhost:5246 ); ``` **Android Emulator:** ```dart final client = CqrsApiClient( config: ApiClientConfig( baseUrl: 'http://10.0.2.2:5246', // Special emulator IP timeout: Duration(seconds: 30), ), ); ``` **Production:** ```dart final client = CqrsApiClient( config: ApiClientConfig( baseUrl: 'https://api.svrnty.com', timeout: Duration(seconds: 30), defaultHeaders: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Bearer $accessToken', }, ), ); ``` Always call `client.dispose()` when done. --- ## Error Handling ### Error Types - `ApiErrorType.network` - No internet/DNS failure - `ApiErrorType.http` - 4xx/5xx responses - `ApiErrorType.serialization` - JSON parsing failed - `ApiErrorType.timeout` - Request took too long - `ApiErrorType.validation` - Backend validation error (422) - `ApiErrorType.unknown` - Unexpected errors ### Handling Patterns ```dart result.when( success: (data) { /* handle success */ }, error: (error) { switch (error.type) { case ApiErrorType.network: showSnackbar('No internet connection'); case ApiErrorType.timeout: showSnackbar('Request timed out'); case ApiErrorType.validation: showValidationErrors(error.details); case ApiErrorType.http: if (error.statusCode == 401) navigateToLogin(); else showSnackbar('Server error: ${error.message}'); default: showSnackbar('Unexpected error: ${error.message}'); } }, ); ``` --- ## Important Workflows ### When Backend Changes API Contract 1. Backend team notifies: "Updated API - added X endpoint" 2. Check backend CHANGELOG: `cat ../backend/docs/CHANGELOG.md` 3. Update contract: `cp ../backend/docs/openapi.json ./api-schema.json` 4. Regenerate: `./scripts/update_api_client.sh` 5. Add endpoint extension if needed (see "Adding New API Endpoints") 6. Run tests: `flutter test` 7. Verify types: `./scripts/verify_api_types.sh` 8. Commit: `git commit -m "feat: Add X endpoint integration"` ### Troubleshooting **Code generation fails:** ```bash flutter clean flutter pub get flutter pub run build_runner build --delete-conflicting-outputs ``` **Type errors after regenerating:** Backend made breaking changes. Check `../backend/docs/CHANGELOG.md` and update code accordingly. **Network error on device:** - iOS/Real device: Use actual IP (e.g., `http://192.168.1.100:5246`) - Android emulator: Use `http://10.0.2.2:5246` **JSON parsing error:** 1. Verify `api-schema.json` matches backend's `docs/openapi.json` 2. Check DTO `fromJson` matches OpenAPI schema 3. Verify backend returned correct content-type --- ## Additional Documentation - **API Integration Guide:** `README_API.md` (comprehensive API documentation) - **Strict Typing Rules:** `.claude-docs/strict-typing.md` (mandatory) - **Response Protocol:** `.claude-docs/response-protocol.md` (mandatory) - **Backend Docs:** `../backend/docs/` (architecture, changelog) - **OpenAPI Contract:** `api-schema.json` (source of truth) --- ## Key Reminders 1. **OpenAPI is Source of Truth** - Always regenerate from `api-schema.json` 2. **CQRS Pattern** - All endpoints use POST with JSON body (even empty `{}`) 3. **Type Safety** - No `dynamic` types, use `Serializable` interface 4. **Functional Errors** - Use `Result`, not try-catch 5. **Monitor Backend CHANGELOG** - Breaking changes documented there 6. **Test Everything** - Unit tests + integration tests 7. **Follow Response Protocol** - All responses end with structured choices 8. **Strict Typing** - Explicit types everywhere, no exceptions